Skip to content

Commit

Permalink
Adds the following attributes:
Browse files Browse the repository at this point in the history
- center
- centerx/y
- angle
- slope

Co-authored-by: Emc2356 <[email protected]>
Co-authored-by: NovialRiptide <[email protected]>
Co-authored-by: ScriptLineStudios <[email protected]>
Co-authored-by: Avaxar <[email protected]>
Co-authored-by: maqa41 <[email protected]>
  • Loading branch information
6 people committed Dec 16, 2024
1 parent 6026d1e commit 561b3f2
Show file tree
Hide file tree
Showing 6 changed files with 326 additions and 0 deletions.
8 changes: 8 additions & 0 deletions buildconfig/stubs/pygame/geometry.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ class Line:
def b(self, value: Point) -> None: ...
@property
def length(self) -> float: ...
@property
def center(self) -> tuple[float, float]: ...
@center.setter
def center(self, value: Point) -> None: ...
@property
def slope(self) -> float: ...
@property
def angle(self) -> float: ...
@overload
def __init__(self, ax: float, ay: float, bx: float, by: float) -> None: ...
@overload
Expand Down
70 changes: 70 additions & 0 deletions docs/reST/ref/geometry.rst
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,76 @@

.. ## Line.length ##
.. attribute:: center

| :sl:`the coordinate of the middle point of the line`
| :sg:`center -> (float, float)`
The center of the line. Calculated using the `((xa + xb) / 2, (ya + yb) / 2)` formula.
It can be reassigned to move the `Line`. If reassigned the `xa`, `ya`, `xb`, `yb`
attributes will be changed in order to produce a `Line` with matching center.

.. versionadded:: 2.5.3

.. ## Line.center ##
.. attribute:: centerx

| :sl:`the x coordinate of the middle point of the line`
| :sg:`centerx -> float`
The `x` coordinate of the center of the line, it's calculated using
the `((xa + xb) / 2)` formula. It can be reassigned to move the `Line`.
If reassigned the `xa` and `xb` attributes will be changed in order to
produce a `Line` with matching center. The `ya` and `yb` attributes will not
be affected.

.. versionadded:: 2.5.3

.. ## Line.centerx ##
.. attribute:: centery

| :sl:`the y coordinate of the middle point of the line`
| :sg:`centery -> float`
The `y` coordinate of the center of the `Line`, it's calculated using
the `((ya + yb) / 2)` formula. It can be reassigned to move the `Line`.
If reassigned the `ya` and `yb` attributes will be changed in order to
produce a `Line` with matching center. The `xa` and `xb` attributes will not
be affected.

.. versionadded:: 2.5.3

.. ## Line.centery ##
.. attribute:: angle

| :sl:`the angle of the line`
| :sg:`angle -> float`
The angle of the line representing its orientation. Calculated using
the `atan2(yb - ya, xb - xa)` formula. This attribute is read-only, it cannot
be reassigned. To change the line's angle use the `rotate` method or change
its `a` or `b` attributes.

.. versionadded:: 2.5.3

.. ## Line.angle ##
.. attribute:: slope

| :sl:`the slope of the line`
| :sg:`slope -> float`
The slope of the line. Calculated using the `(yb - ya) / (xb - xa)` formula.
This attribute is read-only, it cannot be reassigned. To change the line's slope
use the `rotate` method or change its `a` or `b` attributes.

.. versionadded:: 2.5.3

.. ## Line.slope ##
**Line Methods**

----
Expand Down
5 changes: 5 additions & 0 deletions src_c/doc/geometry_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
#define DOC_LINE_A "a -> (float, float)\nthe first point of the line"
#define DOC_LINE_B "b -> (float, float)\nthe second point of the line"
#define DOC_LINE_LENGTH "length -> float\nthe length of the line"
#define DOC_LINE_CENTER "center -> (float, float)\nthe coordinate of the middle point of the line"
#define DOC_LINE_CENTERX "centerx -> float\nthe x coordinate of the middle point of the line"
#define DOC_LINE_CENTERY "centery -> float\nthe y coordinate of the middle point of the line"
#define DOC_LINE_ANGLE "angle -> float\nthe angle of the line"
#define DOC_LINE_SLOPE "slope -> float\nthe slope of the line"
#define DOC_LINE_COPY "copy() -> Line\ncopies the line"
#define DOC_LINE_MOVE "move((x, y)) -> Line\nmove(x, y) -> Line\nmoves the line by a given amount"
#define DOC_LINE_MOVEIP "move_ip((x, y)) -> None\nmove_ip(x, y) -> None\nmoves the line by a given amount"
Expand Down
7 changes: 7 additions & 0 deletions src_c/geometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ static PyTypeObject pgLine_Type;
#define M_PI_QUO_180 0.01745329251994329577
#endif

/* Converts radians to degrees */
static inline double
RAD_TO_DEG(double rad)
{
return rad / M_PI_QUO_180;
}

/* Converts degrees to radians */
static inline double
DEG_TO_RAD(double deg)
Expand Down
117 changes: 117 additions & 0 deletions src_c/line.c
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,115 @@ pg_line_getlength(pgLineObject *self, void *closure)
return PyFloat_FromDouble(pgLine_Length(&self->line));
}

static PyObject *
pg_line_get_center(pgLineObject *self, void *closure)
{
return pg_tuple_couple_from_values_double(
(self->line.ax + self->line.bx) / 2,
(self->line.ay + self->line.by) / 2);
}

static int
pg_line_set_center(pgLineObject *self, PyObject *value, void *closure)
{
double m_x, m_y;
DEL_ATTR_NOT_SUPPORTED_CHECK_NO_NAME(value);
if (!pg_TwoDoublesFromObj(value, &m_x, &m_y)) {
PyErr_SetString(
PyExc_TypeError,
"Invalid center value, expected a sequence of 2 numbers");
return -1;
}

double dx = m_x - (self->line.ax + self->line.bx) / 2;
double dy = m_y - (self->line.ay + self->line.by) / 2;

self->line.ax += dx;
self->line.ay += dy;
self->line.bx += dx;
self->line.by += dy;

return 0;
}

static PyObject *
pg_line_get_centerx(pgLineObject *self, void *closure)
{
return PyFloat_FromDouble((self->line.ax + self->line.bx) / 2);
}

static int
pg_line_set_centerx(pgLineObject *self, PyObject *value, void *closure)
{
double m_x;
DEL_ATTR_NOT_SUPPORTED_CHECK_NO_NAME(value);
if (!pg_DoubleFromObj(value, &m_x)) {
PyErr_SetString(PyExc_TypeError,
"Invalid centerx value, expected a numeric value");
return -1;
}

double dx = m_x - (self->line.ax + self->line.bx) / 2;

self->line.ax += dx;
self->line.bx += dx;

return 0;
}

static PyObject *
pg_line_get_centery(pgLineObject *self, void *closure)
{
return PyFloat_FromDouble((self->line.ay + self->line.by) / 2);
}

static int
pg_line_set_centery(pgLineObject *self, PyObject *value, void *closure)
{
double m_y;
DEL_ATTR_NOT_SUPPORTED_CHECK_NO_NAME(value);
if (!pg_DoubleFromObj(value, &m_y)) {
PyErr_SetString(PyExc_TypeError,
"Invalid centery value, expected a numeric value");
return -1;
}

double dy = m_y - (self->line.ay + self->line.by) / 2;

self->line.ay += dy;
self->line.by += dy;

return 0;
}

static PyObject *
pg_line_getangle(pgLineObject *self, void *closure)
{
double dx = self->line.bx - self->line.ax;

if (dx == 0.0)
return (self->line.by > self->line.ay) ? PyFloat_FromDouble(-90.0)
: PyFloat_FromDouble(90.0);

double dy = self->line.by - self->line.ay;
double gradient = dy / dx;

return PyFloat_FromDouble(-RAD_TO_DEG(atan(gradient)));
}

static PyObject *
pg_line_getslope(pgLineObject *self, void *closure)
{
double dem = self->line.bx - self->line.ax;
if (dem == 0) {
return PyFloat_FromDouble(0);
}

double slope = (self->line.by - self->line.ay) / dem;

return PyFloat_FromDouble(slope);
}

static PyGetSetDef pg_line_getsets[] = {
{"ax", (getter)pg_line_getax, (setter)pg_line_setax, DOC_LINE_AX, NULL},
{"ay", (getter)pg_line_getay, (setter)pg_line_setay, DOC_LINE_AY, NULL},
Expand All @@ -355,6 +464,14 @@ static PyGetSetDef pg_line_getsets[] = {
{"a", (getter)pg_line_geta, (setter)pg_line_seta, DOC_LINE_A, NULL},
{"b", (getter)pg_line_getb, (setter)pg_line_setb, DOC_LINE_B, NULL},
{"length", (getter)pg_line_getlength, NULL, DOC_LINE_LENGTH, NULL},
{"center", (getter)pg_line_get_center, (setter)pg_line_set_center,
DOC_LINE_CENTER, NULL},
{"centerx", (getter)pg_line_get_centerx, (setter)pg_line_set_centerx,
DOC_LINE_CENTERX, NULL},
{"centery", (getter)pg_line_get_centery, (setter)pg_line_set_centery,
DOC_LINE_CENTERY, NULL},
{"angle", (getter)pg_line_getangle, NULL, DOC_LINE_ANGLE, NULL},
{"slope", (getter)pg_line_getslope, NULL, DOC_LINE_SLOPE, NULL},
{NULL, 0, NULL, NULL, NULL}};

static PyTypeObject pgLine_Type = {
Expand Down
119 changes: 119 additions & 0 deletions test/geometry_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2017,6 +2017,125 @@ def test_attrib_length(self):
expected_length = 5.414794548272353
self.assertAlmostEqual(line.length, expected_length)

def test_attrib_center(self):
"""a full test for the center attribute"""
expected_x1 = 10.0
expected_y1 = 2.0
expected_x2 = 5.0
expected_y2 = 6.0
expected_a = expected_x1, expected_y1
expected_b = expected_x2, expected_y2
expected_center = (expected_x1 + expected_x2) / 2, (
expected_y1 + expected_y2
) / 2
line = Line(expected_a, expected_b)

self.assertEqual(line.center, expected_center)

line.center = expected_center[0] - 1, expected_center[1] + 1.321

self.assertEqual(
line.center, (expected_center[0] - 1, expected_center[1] + 1.321)
)

line = Line(0, 0, 1, 0)

for value in (None, [], "1", (1,), [1, 2, 3], 1, 1.2):
with self.assertRaises(TypeError):
line.center = value

with self.assertRaises(AttributeError):
del line.center

def test_attrib_centerx(self):
"""a full test for the centerx attribute"""
expected_x1 = 10.0
expected_y1 = 2.0
expected_x2 = 5.0
expected_y2 = 6.0
expected_a = expected_x1, expected_y1
expected_b = expected_x2, expected_y2
expected_center = (expected_x1 + expected_x2) / 2, (
expected_y1 + expected_y2
) / 2
line = Line(expected_a, expected_b)

self.assertEqual(line.centerx, expected_center[0])

line.centerx = expected_center[0] - 1

self.assertEqual(line.centerx, expected_center[0] - 1)

line = Line(0, 0, 1, 0)

for value in (None, [], "1", (1,), [1, 2, 3]):
with self.assertRaises(TypeError):
line.centerx = value

with self.assertRaises(AttributeError):
del line.centerx

def test_attrib_centery(self):
"""a full test for the centery attribute"""
expected_x1 = 10.0
expected_y1 = 2.0
expected_x2 = 5.0
expected_y2 = 6.0
expected_a = expected_x1, expected_y1
expected_b = expected_x2, expected_y2
expected_center = (expected_x1 + expected_x2) / 2, (
expected_y1 + expected_y2
) / 2
line = Line(expected_a, expected_b)

self.assertEqual(line.centery, expected_center[1])

line.centery = expected_center[1] - 1.321

self.assertEqual(line.centery, expected_center[1] - 1.321)

line = Line(0, 0, 1, 0)

for value in (None, [], "1", (1,), [1, 2, 3]):
with self.assertRaises(TypeError):
line.centery = value

with self.assertRaises(AttributeError):
del line.centery

def test_attrib_angle(self):
"""a full test for the angle attribute"""
expected_angle = -83.93394864782331
line = Line(300.0, 400.0, 400.0, 1341.0)
self.assertEqual(line.angle, expected_angle)

expected_angle = 16.17215901578255
line = Line(300.0, 400.0, 400.0, 371.0)
self.assertEqual(line.angle, expected_angle)

expected_angle = -35.53767779197438
line = Line(45.0, 32.0, 94.0, 67.0)
self.assertEqual(line.angle, expected_angle)

expected_angle = -53.88065915052025
line = Line(544.0, 235.0, 382.0, 13.0)
self.assertEqual(line.angle, expected_angle)

def test_attrib_slope(self):
"""a full test for the slope attribute"""
lines = [
[Line(2, 2, 4, 4), 1, False],
[Line(4.6, 2.3, 1.6, 7.3), -5 / 3, True],
[Line(2, 0, 2, 1), 0, False],
[Line(1.2, 3.2, 4.5, 3.2), 0, False],
]

for l in lines:
if l[2]:
self.assertAlmostEqual(l[0].slope, l[1])
else:
self.assertEqual(l[0].slope, l[1])

def test_meth_copy(self):
line = Line(1, 2, 3, 4)
# check 1 arg passed
Expand Down

0 comments on commit 561b3f2

Please sign in to comment.