From 22ccf842158b8f484117661ca240c398a3209c12 Mon Sep 17 00:00:00 2001 From: XFajk Date: Mon, 14 Apr 2025 11:50:20 +0200 Subject: [PATCH 01/16] implemented the Line.project method with all the tests and docs --- buildconfig/stubs/pygame/geometry.pyi | 1 + docs/reST/ref/code_examples/project1.png | Bin 0 -> 3471 bytes docs/reST/ref/code_examples/project2.png | Bin 0 -> 4016 bytes docs/reST/ref/code_examples/project3.png | Bin 0 -> 4490 bytes docs/reST/ref/geometry.rst | 28 ++++++ src_c/doc/geometry_doc.h | 1 + src_c/line.c | 114 +++++++++++++++++++++++ test/geometry_test.py | 19 ++++ 8 files changed, 163 insertions(+) create mode 100644 docs/reST/ref/code_examples/project1.png create mode 100644 docs/reST/ref/code_examples/project2.png create mode 100644 docs/reST/ref/code_examples/project3.png diff --git a/buildconfig/stubs/pygame/geometry.pyi b/buildconfig/stubs/pygame/geometry.pyi index 43724816ec..8e3d22202e 100644 --- a/buildconfig/stubs/pygame/geometry.pyi +++ b/buildconfig/stubs/pygame/geometry.pyi @@ -197,3 +197,4 @@ class Line: def scale_ip(self, factor_and_origin: Point, /) -> None: ... def flip_ab(self) -> Line: ... def flip_ab_ip(self) -> None: ... + def project(self, point: Point, do_clamp: bool = False) -> Point: ... diff --git a/docs/reST/ref/code_examples/project1.png b/docs/reST/ref/code_examples/project1.png new file mode 100644 index 0000000000000000000000000000000000000000..dccd5221c92dc74b8db0d943e422c6a6bfe4eb90 GIT binary patch literal 3471 zcmd^C>0eXF8lI3y5`+eb3u_W~6nE5u!XXG0kR?FvjZj1=)}T-Yx!4#F5-}xMfhghv zt--BIWwTWg>q2r|(AqXoD2P=_3JQwKK|qg)c;DE2zuZ6Iez_m=o4oTb&&=~aGv_xu zW1>SX%^(95k223mGp=rUCIo5N!;7?QxKK!ko!O&(8~ z1U5S}EIu2d53U-2wC0UfYoL;AcyQnfY0|U%{-qA_Q{}C5zK|ErCr`5)G;=g5hkQ;? zZV)CLQX&~%Hf0sd%MSfqBYQo(ZO}JbTvfe%_Q~hdUvD_cYr;oq$Wn8lPmJUSHXjMh z4RoY%!jXKQf`cpzNE#w6F(kAhn1~fbA(X=UKMFF^gPWrj^yljhuvPhqt{y`) zjP7jSCeXsd5OVikl6KhkrSxVLg<=lrk4%9Yi}IDfL+WqIWBg}azg}2yW9K*h<$S=#1KYF zHa?0R5vKfGdYQLIsvn1L^LJctjiGOyrTu#zu0RgGUsQ-vb=qmJd$)>OR+ALVzZ`WV!KVl8QoM@)*_oVSc-b^Ss-aT7O zw*aQ5aMaB`L(i(Mg4YpDL1TOWg#k02gTHrbVX1;6jQMVd#2v2+rx2r!;Hi$yS#O1* zz%n!SjP*s+65McPDt=+6N}ZM90e?Rk3rlx|%!2(azMQr%De=KBb$M)%mx7}aE@$d` zSB*p#*(CterZHnH(@U-WB5qjB28JE0t^D?u<*G-si|iACuO;mXWMoH=t6Rz8g$8=d zo&>G8ueJY~+D@CL?XKXCP0iY}*OyfUxNJ`dWSLn9HIx1?K{rFTc@#uc!Q3h^}i-mTTIhK0A@IH+HSd$jIj<>RTOK?E>0i zVrIIV)ZO=}WOMH+PA|x8=BdlgXUe=~?u2q0Y|Bi>Qr|k{vW#jmG0b|x0J~P*{$J;Drygwe^G2N-slOUy(Ey9{JE>Lj4Zc_ z=mp@#EOmNZPhXg;))6Y^`>WIUJ9$IJI~m0e;#^N?H|{2_`}6B>A>%m4sk6p}Y59E^ zq2#gn0#|Kzlb69e9_vhVhNG-V&GJ>{^k&T8R0T*WEcN(<>#99LaOQ2)skJ{Q{m0m^ z|6by??R|&k28ilNNiA!cY@nM5k-ZY7@&TKl%$jx}@9gv$!_5;f-pn|r2fZZN&luKa zw9n^a!cK-x6=(AK?dq(|UjI*Ry#^|~8dIHd`5fPlyPFrw+(WNQwoOzI{^5$>f!R8i zI#+yD@?wL-tnR$G_d0r-t5V$|n5FJ~c~mm!GSNTm(U$%oH}978NC?49tI%&5%((Wn zOSWoxJBu$9;PSQciP2}K&acPrt`5fvd

yYb${s+M71n2(5E;_Uo5c;fa$W% zlSR9d2+5dlannHsXZOC;+HIG1RmKu05BRun4D>Zl(lM?R-Y`*oBY76VY}}`RELM=^ zBcyjJ+=Cmy>wy;vDRdw{k$1J4^yT_<4MFJ&PG*JP_VzyVv72n|?aEG@BDlwtZ1w#6 zw%A9LYoYrKw)&-Vv0$L(JVRtIxhfgRdGFB7(Q-s)zl=dap%Xq}%!?&Uqp1{55IL?uZS*OR~WI6cfQvX(jF z0#B`XL$z3zyn{>wzEUzLcUmF4p3srDyUD^kQp2^a@NH zEq%9&++MIU(e!2nC8RSrLR*44b5qTvnEm8-GcX+qrWpk1u=$=sQscu@HJ`7)A*obw z)~wC4y)i#j_Y3P8RET77ibAOo5vBRnAzgdNA!js-(nLZ28wCeWH5zlm^{!fa3t)Qy zSpQyfyAiBg!Ew#3ne;S%;_Hih?qUY|TozC7`0C~3UF3F)d}f#%KIV)k2a+hX_NwHw zvkeiHX7BaS_2vfnk_JihnTb{9R!tFV*d;$QDy;oWGQgv6G63DK=3Do z@)3BSJL75l{b6*+;SA6OfbI=7;BKuj*05G^K7pv^*4PRsDXI#jV#@q^@gGWQ!e5@p$?6>-%dfz8AMO5ZeEnQfx;GDPeLuaF@%yNJO zQTRlWsKwrJH6@=ZPh#^+0Y>J5hkcf1d${JY_ryx4tE<8w^-@Z*Sdi>lC83&==1@>bMnF&H}Qt+eg*pg>tC0nO$k$ z)kBjvzzClH9pnWeiG-^aIyR&n77=|2wv@}o*nn3QNL@;So>|S>m}#?RWOAh=FdN%g zJ^Uph2_ezEiX7?}XLmWd<;P~a=4QiRp%er3TS)8?>q#StJ*Px1dmb%pe3v|mx3dJ;&SYbrq8L1 zWLC{2v?aK)N%Wz?MT!KKqmGBZ61jo%1sB@~ZScGuD|VZ9fF^gSFf*d`wg*!_9VT*Y zd}3ps3>$mCI&b35aO|5?N>#NVYPnLYj~{T7S8$B6qsJM#gxj={hp*51eoPkfP*q># zNaSx<>J(25XG-ji)qd}-KSM`3_p|;uaoweUt?DOa6#9QX3^bg&Iqv6WbKwO4{cMPm z)ZcB)_C_t=9kXiNG!l7FV~N6EJULNN%hbX3hf;i=nEa2P6aoyJV3wN2q;y|s*a?p) N6dn>CT(>Ma|6k(roG<_Y literal 0 HcmV?d00001 diff --git a/docs/reST/ref/code_examples/project2.png b/docs/reST/ref/code_examples/project2.png new file mode 100644 index 0000000000000000000000000000000000000000..51ca189590babcbdfbf20d723bb1085c480aa9f6 GIT binary patch literal 4016 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 Zo#-)<`iCe#>q~fLBe{Qw?5I?o_HX&r$~pi5 literal 0 HcmV?d00001 diff --git a/docs/reST/ref/code_examples/project3.png b/docs/reST/ref/code_examples/project3.png new file mode 100644 index 0000000000000000000000000000000000000000..f54a85c2cd8b51b5927e922c93ce98196cc4fb6b GIT binary patch literal 4490 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 P_v5+9dttF#;Qs#r;$K point` + + Returns a new point(tuple[float, float]) that is projected onto the line like so + + .. figure:: code_examples/project1.png + :alt: project method image + + Example of what project method does if you input the green point you get back the yellow one. + + + .. figure:: code_examples/project2.png + :alt: project do_clamp=False image + + Example of what the do_clamp argument does when it is False + + + .. figure:: code_examples/project1.png + :alt: project do_clamp=True image + + Example of what the do_clamp argument does when it is True + + .. versionadded:: 2.5.4 + + .. ## Line.project ## diff --git a/src_c/doc/geometry_doc.h b/src_c/doc/geometry_doc.h index 7873df226f..3dce1dec6b 100644 --- a/src_c/doc/geometry_doc.h +++ b/src_c/doc/geometry_doc.h @@ -45,3 +45,4 @@ #define DOC_LINE_SCALEIP "scale_ip(factor, origin) -> None\nscale_ip(factor_and_origin) -> None\nscales the line by the given factor from the given origin in place" #define DOC_LINE_FLIPAB "flip_ab() -> Line\nflips the line a and b points" #define DOC_LINE_FLIPABIP "flip_ab_ip() -> None\nflips the line a and b points, in place" +#define DOC_LINE_PROJECT "project(point, do_clamp=False) -> point\nprojects the line onto the given line" diff --git a/src_c/line.c b/src_c/line.c index ad25ed4866..aec3eec5b3 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -179,6 +179,70 @@ _line_scale_helper(pgLineBase *line, double factor, double origin) return 1; } +void +_normalize_vector(double *vector) +{ + double length = sqrt(vector[0] * vector[0] + vector[1] * vector[1]); + // check to see if the vector is zero + if (length == 0) { + vector[0] = 0; + vector[1] = 0; + } + else { + vector[0] /= length; + vector[1] /= length; + } +} + +double +_length_of_vector(double *vector) +{ + return sqrt(vector[0] * vector[0] + vector[1] * vector[1]); +} + +static PyObject * +_line_project_helper(pgLineBase *line, double *point, int do_clamp) +{ + // this is a vector that goes from one point of the line to another + double line_vector[2] = {line->bx - line->ax, line->by - line->ay}; + double line_length = _length_of_vector(line_vector); + + // this is a unit vector that points in the direction of the line + double normalized_line_vector[2] = {line_vector[0], line_vector[1]}; + _normalize_vector(normalized_line_vector); + + // this is a vector that goes from the start of the line to the point we + // are projecting onto the line + double vector_from_line_start_to_point[2] = {point[0] - line->ax, + point[1] - line->ay}; + + double dot_product = + vector_from_line_start_to_point[0] * normalized_line_vector[0] + + vector_from_line_start_to_point[1] * normalized_line_vector[1]; + + double projection[2] = {dot_product * normalized_line_vector[0], + dot_product * normalized_line_vector[1]}; + + if (do_clamp) { + if (dot_product > line_length) { + projection[0] = line_vector[0]; + projection[1] = line_vector[1]; + } + else if (dot_product < 0) { + projection[0] = 0; + projection[1] = 0; + } + } + + double projected_point[2] = {line->ax + projection[0], + line->ay + projection[1]}; + + PyObject *projected_tuple = + Py_BuildValue("(dd)", projected_point[0], projected_point[1]); + + return projected_tuple; +} + static PyObject * pg_line_scale(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) { @@ -219,6 +283,54 @@ pg_line_scale_ip(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) Py_RETURN_NONE; } +static PyObject * +pg_line_project(pgLineObject *self, PyObject *args, PyObject *kwnames) +{ + PyObject *point_obj = NULL; + int do_clamp = 0; + + static char *kwlist[] = {"point", "do_clamp", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwnames, "O|p:project", kwlist, + &point_obj, &do_clamp)) { + return RAISE( + PyExc_TypeError, + "project requires a sequence(point) and an optional clamp flag"); + } + + PyObject *item; + double point[2]; + + if (!PySequence_Check(point_obj) || PySequence_Size(point_obj) != 2) { + return RAISE( + PyExc_ValueError, + "project requires the point to be a sequence of 2 elements"); + } + + item = PySequence_GetItem(point_obj, 0); + point[0] = PyFloat_AsDouble(item); + + PyObject *error_type; + if ((error_type = PyErr_Occurred())) { + return NULL; + } + + item = PySequence_GetItem(point_obj, 1); + point[1] = PyFloat_AsDouble(item); + + if ((error_type = PyErr_Occurred())) { + return NULL; + } + + PyObject *projected_point; + if (!(projected_point = + _line_project_helper(&pgLine_AsLine(self), point, do_clamp))) { + return NULL; + } + + return projected_point; +} + static struct PyMethodDef pg_line_methods[] = { {"__copy__", (PyCFunction)pg_line_copy, METH_NOARGS, DOC_LINE_COPY}, {"copy", (PyCFunction)pg_line_copy, METH_NOARGS, DOC_LINE_COPY}, @@ -231,6 +343,8 @@ static struct PyMethodDef pg_line_methods[] = { {"scale", (PyCFunction)pg_line_scale, METH_FASTCALL, DOC_LINE_SCALE}, {"scale_ip", (PyCFunction)pg_line_scale_ip, METH_FASTCALL, DOC_LINE_SCALEIP}, + {"project", (PyCFunction)pg_line_project, METH_VARARGS | METH_KEYWORDS, + ""}, {NULL, NULL, 0, NULL}}; static PyObject * diff --git a/test/geometry_test.py b/test/geometry_test.py index c4d48e0d6a..c5b4c9f6c2 100644 --- a/test/geometry_test.py +++ b/test/geometry_test.py @@ -2201,6 +2201,25 @@ def test_meth_update(self): with self.assertRaises(TypeError): line.update(1, 2, 3) + def test_meth_project(self): + line = Line(0, 0, 100, 100) + test_point1 = (25, 75) + test_clamp_point1 = (100, 300) + test_clamp_point2 = (-50, -150) + + projected_point = line.project(test_point1) + self.assertEqual(math.ceil(projected_point[0]), 50) + self.assertEqual(math.ceil(projected_point[1]), 50) + + projected_point = line.project(test_clamp_point1, do_clamp=True) + self.assertEqual(math.ceil(projected_point[0]), 100) + self.assertEqual(math.ceil (projected_point[1]), 100) + + projected_point = line.project(test_clamp_point2, do_clamp=True) + self.assertEqual(math.ceil(projected_point[0]), 0) + self.assertEqual(math.ceil(projected_point[1]), 0) + + def test__str__(self): """Checks whether the __str__ method works correctly.""" l_str = "Line((10.1, 10.2), (4.3, 56.4))" From e2001ddb932e1e930b5e4219f2cc666cda5bd76e Mon Sep 17 00:00:00 2001 From: XFajk Date: Mon, 14 Apr 2025 12:38:21 +0200 Subject: [PATCH 02/16] added the doc string to the method in C --- src_c/line.c | 2 +- test/geometry_test.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src_c/line.c b/src_c/line.c index aec3eec5b3..525c374f9e 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -344,7 +344,7 @@ static struct PyMethodDef pg_line_methods[] = { {"scale_ip", (PyCFunction)pg_line_scale_ip, METH_FASTCALL, DOC_LINE_SCALEIP}, {"project", (PyCFunction)pg_line_project, METH_VARARGS | METH_KEYWORDS, - ""}, + DOC_LINE_PROJECT}, {NULL, NULL, 0, NULL}}; static PyObject * diff --git a/test/geometry_test.py b/test/geometry_test.py index c5b4c9f6c2..d98ff444f3 100644 --- a/test/geometry_test.py +++ b/test/geometry_test.py @@ -2213,13 +2213,12 @@ def test_meth_project(self): projected_point = line.project(test_clamp_point1, do_clamp=True) self.assertEqual(math.ceil(projected_point[0]), 100) - self.assertEqual(math.ceil (projected_point[1]), 100) + self.assertEqual(math.ceil(projected_point[1]), 100) projected_point = line.project(test_clamp_point2, do_clamp=True) self.assertEqual(math.ceil(projected_point[0]), 0) self.assertEqual(math.ceil(projected_point[1]), 0) - def test__str__(self): """Checks whether the __str__ method works correctly.""" l_str = "Line((10.1, 10.2), (4.3, 56.4))" From 193071aeb3edfee35d0d5b021c0dc51d015c37e0 Mon Sep 17 00:00:00 2001 From: XFajk Date: Tue, 15 Apr 2025 17:47:31 +0200 Subject: [PATCH 03/16] optimized the Line.project method made it METH_FASTCALL and removed sqrt calls --- src_c/line.c | 75 ++++++++++++++++++---------------------------------- 1 file changed, 25 insertions(+), 50 deletions(-) diff --git a/src_c/line.c b/src_c/line.c index 525c374f9e..b42b603da5 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -201,15 +201,10 @@ _length_of_vector(double *vector) } static PyObject * -_line_project_helper(pgLineBase *line, double *point, int do_clamp) +_line_project_helper(pgLineBase *line, double *point, int clamp) { // this is a vector that goes from one point of the line to another double line_vector[2] = {line->bx - line->ax, line->by - line->ay}; - double line_length = _length_of_vector(line_vector); - - // this is a unit vector that points in the direction of the line - double normalized_line_vector[2] = {line_vector[0], line_vector[1]}; - _normalize_vector(normalized_line_vector); // this is a vector that goes from the start of the line to the point we // are projecting onto the line @@ -217,14 +212,17 @@ _line_project_helper(pgLineBase *line, double *point, int do_clamp) point[1] - line->ay}; double dot_product = - vector_from_line_start_to_point[0] * normalized_line_vector[0] + - vector_from_line_start_to_point[1] * normalized_line_vector[1]; + (vector_from_line_start_to_point[0] * line_vector[0] + + vector_from_line_start_to_point[1] * line_vector[1]) / + (line_vector[0] * line_vector[0] + line_vector[1] * line_vector[1]); - double projection[2] = {dot_product * normalized_line_vector[0], - dot_product * normalized_line_vector[1]}; + double projection[2] = {dot_product * line_vector[0], + dot_product * line_vector[1]}; - if (do_clamp) { - if (dot_product > line_length) { + if (clamp) { + if (projection[0] * projection[0] + projection[1] * projection[1] > + line_vector[0] * line_vector[0] + + line_vector[1] * line_vector[1]) { projection[0] = line_vector[0]; projection[1] = line_vector[1]; } @@ -237,10 +235,8 @@ _line_project_helper(pgLineBase *line, double *point, int do_clamp) double projected_point[2] = {line->ax + projection[0], line->ay + projection[1]}; - PyObject *projected_tuple = - Py_BuildValue("(dd)", projected_point[0], projected_point[1]); - - return projected_tuple; + return pg_tuple_couple_from_values_double(projected_point[0], + projected_point[1]); } static PyObject * @@ -284,47 +280,26 @@ pg_line_scale_ip(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) } static PyObject * -pg_line_project(pgLineObject *self, PyObject *args, PyObject *kwnames) +pg_line_project(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs, + PyObject *kwnames) { - PyObject *point_obj = NULL; - int do_clamp = 0; + double point[2] = {0.f, 0.f}; + int clamp = 0; - static char *kwlist[] = {"point", "do_clamp", NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, kwnames, "O|p:project", kwlist, - &point_obj, &do_clamp)) { - return RAISE( - PyExc_TypeError, - "project requires a sequence(point) and an optional clamp flag"); - } - - PyObject *item; - double point[2]; - - if (!PySequence_Check(point_obj) || PySequence_Size(point_obj) != 2) { - return RAISE( - PyExc_ValueError, - "project requires the point to be a sequence of 2 elements"); - } - - item = PySequence_GetItem(point_obj, 0); - point[0] = PyFloat_AsDouble(item); - - PyObject *error_type; - if ((error_type = PyErr_Occurred())) { - return NULL; + if (nargs >= 1) { + if (!pg_TwoDoublesFromObj(args[0], &point[0], &point[1])) { + return RAISE(PyExc_TypeError, + "project requires a sequence of two numbers"); + } } - item = PySequence_GetItem(point_obj, 1); - point[1] = PyFloat_AsDouble(item); - - if ((error_type = PyErr_Occurred())) { - return NULL; + if (kwnames != NULL) { + clamp = PyObject_IsTrue(args[nargs]); } PyObject *projected_point; if (!(projected_point = - _line_project_helper(&pgLine_AsLine(self), point, do_clamp))) { + _line_project_helper(&pgLine_AsLine(self), point, clamp))) { return NULL; } @@ -343,7 +318,7 @@ static struct PyMethodDef pg_line_methods[] = { {"scale", (PyCFunction)pg_line_scale, METH_FASTCALL, DOC_LINE_SCALE}, {"scale_ip", (PyCFunction)pg_line_scale_ip, METH_FASTCALL, DOC_LINE_SCALEIP}, - {"project", (PyCFunction)pg_line_project, METH_VARARGS | METH_KEYWORDS, + {"project", (PyCFunction)pg_line_project, METH_FASTCALL | METH_KEYWORDS, DOC_LINE_PROJECT}, {NULL, NULL, 0, NULL}}; From ea76d39201ac85672b1dddbe83b746102b30c555 Mon Sep 17 00:00:00 2001 From: XFajk Date: Tue, 15 Apr 2025 17:48:28 +0200 Subject: [PATCH 04/16] made the docs more clear, improved the images --- buildconfig/stubs/pygame/geometry.pyi | 4 +++- docs/reST/ref/code_examples/project.png | Bin 0 -> 14532 bytes docs/reST/ref/code_examples/project1.png | Bin 3471 -> 0 bytes docs/reST/ref/code_examples/project2.png | Bin 4016 -> 0 bytes docs/reST/ref/code_examples/project3.png | Bin 4490 -> 0 bytes docs/reST/ref/code_examples/project_clamp.png | Bin 0 -> 30877 bytes docs/reST/ref/geometry.rst | 20 +++++++----------- src_c/doc/geometry_doc.h | 2 +- 8 files changed, 12 insertions(+), 14 deletions(-) create mode 100644 docs/reST/ref/code_examples/project.png delete mode 100644 docs/reST/ref/code_examples/project1.png delete mode 100644 docs/reST/ref/code_examples/project2.png delete mode 100644 docs/reST/ref/code_examples/project3.png create mode 100644 docs/reST/ref/code_examples/project_clamp.png diff --git a/buildconfig/stubs/pygame/geometry.pyi b/buildconfig/stubs/pygame/geometry.pyi index 8e3d22202e..d745f16557 100644 --- a/buildconfig/stubs/pygame/geometry.pyi +++ b/buildconfig/stubs/pygame/geometry.pyi @@ -197,4 +197,6 @@ class Line: def scale_ip(self, factor_and_origin: Point, /) -> None: ... def flip_ab(self) -> Line: ... def flip_ab_ip(self) -> None: ... - def project(self, point: Point, do_clamp: bool = False) -> Point: ... + def project( + self, point: tuple[float, float], clamp: bool = False + ) -> tuple[float, float]: ... diff --git a/docs/reST/ref/code_examples/project.png b/docs/reST/ref/code_examples/project.png new file mode 100644 index 0000000000000000000000000000000000000000..3177bf05f672f04846906f2829161365a21851d5 GIT binary patch literal 14532 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

s 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 literal 0 HcmV?d00001 diff --git a/docs/reST/ref/code_examples/project1.png b/docs/reST/ref/code_examples/project1.png deleted file mode 100644 index dccd5221c92dc74b8db0d943e422c6a6bfe4eb90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3471 zcmd^C>0eXF8lI3y5`+eb3u_W~6nE5u!XXG0kR?FvjZj1=)}T-Yx!4#F5-}xMfhghv zt--BIWwTWg>q2r|(AqXoD2P=_3JQwKK|qg)c;DE2zuZ6Iez_m=o4oTb&&=~aGv_xu zW1>SX%^(95k223mGp=rUCIo5N!;7?QxKK!ko!O&(8~ z1U5S}EIu2d53U-2wC0UfYoL;AcyQnfY0|U%{-qA_Q{}C5zK|ErCr`5)G;=g5hkQ;? zZV)CLQX&~%Hf0sd%MSfqBYQo(ZO}JbTvfe%_Q~hdUvD_cYr;oq$Wn8lPmJUSHXjMh z4RoY%!jXKQf`cpzNE#w6F(kAhn1~fbA(X=UKMFF^gPWrj^yljhuvPhqt{y`) zjP7jSCeXsd5OVikl6KhkrSxVLg<=lrk4%9Yi}IDfL+WqIWBg}azg}2yW9K*h<$S=#1KYF zHa?0R5vKfGdYQLIsvn1L^LJctjiGOyrTu#zu0RgGUsQ-vb=qmJd$)>OR+ALVzZ`WV!KVl8QoM@)*_oVSc-b^Ss-aT7O zw*aQ5aMaB`L(i(Mg4YpDL1TOWg#k02gTHrbVX1;6jQMVd#2v2+rx2r!;Hi$yS#O1* zz%n!SjP*s+65McPDt=+6N}ZM90e?Rk3rlx|%!2(azMQr%De=KBb$M)%mx7}aE@$d` zSB*p#*(CterZHnH(@U-WB5qjB28JE0t^D?u<*G-si|iACuO;mXWMoH=t6Rz8g$8=d zo&>G8ueJY~+D@CL?XKXCP0iY}*OyfUxNJ`dWSLn9HIx1?K{rFTc@#uc!Q3h^}i-mTTIhK0A@IH+HSd$jIj<>RTOK?E>0i zVrIIV)ZO=}WOMH+PA|x8=BdlgXUe=~?u2q0Y|Bi>Qr|k{vW#jmG0b|x0J~P*{$J;Drygwe^G2N-slOUy(Ey9{JE>Lj4Zc_ z=mp@#EOmNZPhXg;))6Y^`>WIUJ9$IJI~m0e;#^N?H|{2_`}6B>A>%m4sk6p}Y59E^ zq2#gn0#|Kzlb69e9_vhVhNG-V&GJ>{^k&T8R0T*WEcN(<>#99LaOQ2)skJ{Q{m0m^ z|6by??R|&k28ilNNiA!cY@nM5k-ZY7@&TKl%$jx}@9gv$!_5;f-pn|r2fZZN&luKa zw9n^a!cK-x6=(AK?dq(|UjI*Ry#^|~8dIHd`5fPlyPFrw+(WNQwoOzI{^5$>f!R8i zI#+yD@?wL-tnR$G_d0r-t5V$|n5FJ~c~mm!GSNTm(U$%oH}978NC?49tI%&5%((Wn zOSWoxJBu$9;PSQciP2}K&acPrt`5fvd

yYb${s+M71n2(5E;_Uo5c;fa$W% zlSR9d2+5dlannHsXZOC;+HIG1RmKu05BRun4D>Zl(lM?R-Y`*oBY76VY}}`RELM=^ zBcyjJ+=Cmy>wy;vDRdw{k$1J4^yT_<4MFJ&PG*JP_VzyVv72n|?aEG@BDlwtZ1w#6 zw%A9LYoYrKw)&-Vv0$L(JVRtIxhfgRdGFB7(Q-s)zl=dap%Xq}%!?&Uqp1{55IL?uZS*OR~WI6cfQvX(jF z0#B`XL$z3zyn{>wzEUzLcUmF4p3srDyUD^kQp2^a@NH zEq%9&++MIU(e!2nC8RSrLR*44b5qTvnEm8-GcX+qrWpk1u=$=sQscu@HJ`7)A*obw z)~wC4y)i#j_Y3P8RET77ibAOo5vBRnAzgdNA!js-(nLZ28wCeWH5zlm^{!fa3t)Qy zSpQyfyAiBg!Ew#3ne;S%;_Hih?qUY|TozC7`0C~3UF3F)d}f#%KIV)k2a+hX_NwHw zvkeiHX7BaS_2vfnk_JihnTb{9R!tFV*d;$QDy;oWGQgv6G63DK=3Do z@)3BSJL75l{b6*+;SA6OfbI=7;BKuj*05G^K7pv^*4PRsDXI#jV#@q^@gGWQ!e5@p$?6>-%dfz8AMO5ZeEnQfx;GDPeLuaF@%yNJO zQTRlWsKwrJH6@=ZPh#^+0Y>J5hkcf1d${JY_ryx4tE<8w^-@Z*Sdi>lC83&==1@>bMnF&H}Qt+eg*pg>tC0nO$k$ z)kBjvzzClH9pnWeiG-^aIyR&n77=|2wv@}o*nn3QNL@;So>|S>m}#?RWOAh=FdN%g zJ^Uph2_ezEiX7?}XLmWd<;P~a=4QiRp%er3TS)8?>q#StJ*Px1dmb%pe3v|mx3dJ;&SYbrq8L1 zWLC{2v?aK)N%Wz?MT!KKqmGBZ61jo%1sB@~ZScGuD|VZ9fF^gSFf*d`wg*!_9VT*Y zd}3ps3>$mCI&b35aO|5?N>#NVYPnLYj~{T7S8$B6qsJM#gxj={hp*51eoPkfP*q># zNaSx<>J(25XG-ji)qd}-KSM`3_p|;uaoweUt?DOa6#9QX3^bg&Iqv6WbKwO4{cMPm z)ZcB)_C_t=9kXiNG!l7FV~N6EJULNN%hbX3hf;i=nEa2P6aoyJV3wN2q;y|s*a?p) N6dn>CT(>Ma|6k(roG<_Y diff --git a/docs/reST/ref/code_examples/project2.png b/docs/reST/ref/code_examples/project2.png deleted file mode 100644 index 51ca189590babcbdfbf20d723bb1085c480aa9f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4016 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 Zo#-)<`iCe#>q~fLBe{Qw?5I?o_HX&r$~pi5 diff --git a/docs/reST/ref/code_examples/project3.png b/docs/reST/ref/code_examples/project3.png deleted file mode 100644 index f54a85c2cd8b51b5927e922c93ce98196cc4fb6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4490 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 z 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 A-T(jq literal 0 HcmV?d00001 diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst index 8ebc3f8732..051ece46a0 100644 --- a/docs/reST/ref/geometry.rst +++ b/docs/reST/ref/geometry.rst @@ -744,26 +744,22 @@ .. method:: project | :sl:`projects the line onto the given line` - | :sg:`project(point, do_clamp=False) -> point` + | :sg:`project(point: tuple[float, float], clamp=False) -> tuple[float, float]` - Returns a new point(tuple[float, float]) that is projected onto the line like so + This method takes in a point and one boolean keyword argument clamp. It outputs an orthogonally projected point onto the line. + If clamp is True it makes sure that the outputted point will be on the line segment. - .. figure:: code_examples/project1.png - :alt: project method image - - Example of what project method does if you input the green point you get back the yellow one. + .. figure:: code_examples/project.png + :alt: project method image - .. figure:: code_examples/project2.png - :alt: project do_clamp=False image - - Example of what the do_clamp argument does when it is False + Example of how it projects the point on to the line purplish point is the point we want to project and the blue point is what you would get as a result - .. figure:: code_examples/project1.png + .. figure:: code_examples/project_clamp.png :alt: project do_clamp=True image - Example of what the do_clamp argument does when it is True + Example of what the clamp argument changes if it is true it stays on the line segment .. versionadded:: 2.5.4 diff --git a/src_c/doc/geometry_doc.h b/src_c/doc/geometry_doc.h index 3dce1dec6b..6c061840d3 100644 --- a/src_c/doc/geometry_doc.h +++ b/src_c/doc/geometry_doc.h @@ -45,4 +45,4 @@ #define DOC_LINE_SCALEIP "scale_ip(factor, origin) -> None\nscale_ip(factor_and_origin) -> None\nscales the line by the given factor from the given origin in place" #define DOC_LINE_FLIPAB "flip_ab() -> Line\nflips the line a and b points" #define DOC_LINE_FLIPABIP "flip_ab_ip() -> None\nflips the line a and b points, in place" -#define DOC_LINE_PROJECT "project(point, do_clamp=False) -> point\nprojects the line onto the given line" +#define DOC_LINE_PROJECT "project(point: tuple[float, float], clamp=False) -> tuple[float, float]\nprojects the line onto the given line" From 44ab4fc23d5c2a8084d6eb16117ebbaefdfd2bc0 Mon Sep 17 00:00:00 2001 From: Rudolf Vrbensky <89221002+XFajk@users.noreply.github.com> Date: Wed, 16 Apr 2025 11:45:16 +0200 Subject: [PATCH 05/16] Update docs/reST/ref/geometry.rst clarify the docs for the Line.project methdo Co-authored-by: aatle <168398276+aatle@users.noreply.github.com> --- docs/reST/ref/geometry.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst index 051ece46a0..ab9579002e 100644 --- a/docs/reST/ref/geometry.rst +++ b/docs/reST/ref/geometry.rst @@ -747,7 +747,8 @@ | :sg:`project(point: tuple[float, float], clamp=False) -> tuple[float, float]` This method takes in a point and one boolean keyword argument clamp. It outputs an orthogonally projected point onto the line. - If clamp is True it makes sure that the outputted point will be on the line segment. + If clamp is `True` it makes sure that the outputted point will be on the line segment (which might not be orthogonal), and if it is `False` (the default) then any point on the infinitely extended line may be outputted. + This method can be used to find the closest point on a line to the given point. The output is the unique point on the line or line segment that is the smallest distance away from the given point. .. figure:: code_examples/project.png From cd9c648cd4b2c52d5d4d92880a35bdee5a1e5e05 Mon Sep 17 00:00:00 2001 From: Rudolf Vrbensky <89221002+XFajk@users.noreply.github.com> Date: Wed, 16 Apr 2025 11:46:03 +0200 Subject: [PATCH 06/16] Update docs/reST/ref/geometry.rst grammar fix in the docs for the Line.project method Co-authored-by: aatle <168398276+aatle@users.noreply.github.com> --- docs/reST/ref/geometry.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst index ab9579002e..c4d1d9c35a 100644 --- a/docs/reST/ref/geometry.rst +++ b/docs/reST/ref/geometry.rst @@ -754,7 +754,7 @@ .. figure:: code_examples/project.png :alt: project method image - Example of how it projects the point on to the line purplish point is the point we want to project and the blue point is what you would get as a result + Example of how it projects the point onto the line. The red point is the point we want to project and the blue point is what you would get as a result. .. figure:: code_examples/project_clamp.png From 896f7b28c6f4ad7b2a542c7b36b7113da7e7fc6f Mon Sep 17 00:00:00 2001 From: Rudolf Vrbensky <89221002+XFajk@users.noreply.github.com> Date: Wed, 16 Apr 2025 11:48:32 +0200 Subject: [PATCH 07/16] Update docs/reST/ref/geometry.rst small update to the docs for Line.project Co-authored-by: aatle <168398276+aatle@users.noreply.github.com> --- docs/reST/ref/geometry.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst index c4d1d9c35a..a9c91795d3 100644 --- a/docs/reST/ref/geometry.rst +++ b/docs/reST/ref/geometry.rst @@ -758,9 +758,9 @@ .. figure:: code_examples/project_clamp.png - :alt: project do_clamp=True image + :alt: project clamp argument image - Example of what the clamp argument changes if it is true it stays on the line segment + Example of what the clamp argument changes. If it is `True`, the point is bounded between the line segment ends. .. versionadded:: 2.5.4 From ed8af87e238af6c099d66309cc9c44bf8b4621b5 Mon Sep 17 00:00:00 2001 From: XFajk Date: Tue, 15 Apr 2025 18:02:52 +0200 Subject: [PATCH 08/16] added "/" to the stub file to indicate that the first arg is positional only --- buildconfig/stubs/pygame/geometry.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildconfig/stubs/pygame/geometry.pyi b/buildconfig/stubs/pygame/geometry.pyi index d745f16557..0c8919cac5 100644 --- a/buildconfig/stubs/pygame/geometry.pyi +++ b/buildconfig/stubs/pygame/geometry.pyi @@ -198,5 +198,5 @@ class Line: def flip_ab(self) -> Line: ... def flip_ab_ip(self) -> None: ... def project( - self, point: tuple[float, float], clamp: bool = False + self, point: tuple[float, float], / clamp: bool = False ) -> tuple[float, float]: ... From bfe2698a47e7a766817ab03b0c495b18854a952f Mon Sep 17 00:00:00 2001 From: XFajk Date: Wed, 16 Apr 2025 12:41:04 +0200 Subject: [PATCH 09/16] added one more test case fixed the stub --- buildconfig/stubs/pygame/geometry.pyi | 2 +- test/geometry_test.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/buildconfig/stubs/pygame/geometry.pyi b/buildconfig/stubs/pygame/geometry.pyi index 0c8919cac5..3a62fe601a 100644 --- a/buildconfig/stubs/pygame/geometry.pyi +++ b/buildconfig/stubs/pygame/geometry.pyi @@ -198,5 +198,5 @@ class Line: def flip_ab(self) -> Line: ... def flip_ab_ip(self) -> None: ... def project( - self, point: tuple[float, float], / clamp: bool = False + self, point: Point, / clamp: bool = False ) -> tuple[float, float]: ... diff --git a/test/geometry_test.py b/test/geometry_test.py index d98ff444f3..6e09cd2cc1 100644 --- a/test/geometry_test.py +++ b/test/geometry_test.py @@ -2206,16 +2206,21 @@ def test_meth_project(self): test_point1 = (25, 75) test_clamp_point1 = (100, 300) test_clamp_point2 = (-50, -150) + test_clamp_point3 = (-200, -200) projected_point = line.project(test_point1) self.assertEqual(math.ceil(projected_point[0]), 50) self.assertEqual(math.ceil(projected_point[1]), 50) - projected_point = line.project(test_clamp_point1, do_clamp=True) + projected_point = line.project(test_clamp_point1, clamp=True) self.assertEqual(math.ceil(projected_point[0]), 100) self.assertEqual(math.ceil(projected_point[1]), 100) - projected_point = line.project(test_clamp_point2, do_clamp=True) + projected_point = line.project(test_clamp_point2, clamp=True) + self.assertEqual(math.ceil(projected_point[0]), 0) + self.assertEqual(math.ceil(projected_point[1]), 0) + + projected_point = line.project(test_clamp_point3, clamp=True) self.assertEqual(math.ceil(projected_point[0]), 0) self.assertEqual(math.ceil(projected_point[1]), 0) From a2913292afa7a81655a1be36525afa026cb4c685 Mon Sep 17 00:00:00 2001 From: XFajk Date: Wed, 16 Apr 2025 15:11:59 +0200 Subject: [PATCH 10/16] made a small change to the stub --- buildconfig/stubs/pygame/geometry.pyi | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/buildconfig/stubs/pygame/geometry.pyi b/buildconfig/stubs/pygame/geometry.pyi index 3a62fe601a..985d6050cb 100644 --- a/buildconfig/stubs/pygame/geometry.pyi +++ b/buildconfig/stubs/pygame/geometry.pyi @@ -197,6 +197,4 @@ class Line: def scale_ip(self, factor_and_origin: Point, /) -> None: ... def flip_ab(self) -> Line: ... def flip_ab_ip(self) -> None: ... - def project( - self, point: Point, / clamp: bool = False - ) -> tuple[float, float]: ... + def project(self, point: Point, /, clamp: bool = False) -> tuple[float, float]: ... From 317719e3348e6a9afd35bb96cac1542e8f96078d Mon Sep 17 00:00:00 2001 From: XFajk Date: Wed, 16 Apr 2025 15:18:42 +0200 Subject: [PATCH 11/16] fixed a bug in the Line.project method --- src_c/line.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src_c/line.c b/src_c/line.c index b42b603da5..066a0b2aad 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -220,16 +220,17 @@ _line_project_helper(pgLineBase *line, double *point, int clamp) dot_product * line_vector[1]}; if (clamp) { - if (projection[0] * projection[0] + projection[1] * projection[1] > - line_vector[0] * line_vector[0] + - line_vector[1] * line_vector[1]) { - projection[0] = line_vector[0]; - projection[1] = line_vector[1]; - } - else if (dot_product < 0) { + if (dot_product < 0) { projection[0] = 0; projection[1] = 0; } + else if (projection[0] * projection[0] + + projection[1] * projection[1] > + line_vector[0] * line_vector[0] + + line_vector[1] * line_vector[1]) { + projection[0] = line_vector[0]; + projection[1] = line_vector[1]; + } } double projected_point[2] = {line->ax + projection[0], From e6c1f8cd021d052f70dda30d7ce03f77f4000e92 Mon Sep 17 00:00:00 2001 From: XFajk Date: Tue, 29 Apr 2025 18:17:43 +0200 Subject: [PATCH 12/16] remade the method to be VARARGS insted of FASTCALL --- .clangd | 2 ++ .my_pygame_test/main.py | 27 +++++++++++++++++++++++ src_c/line.c | 47 ++++++++++++++--------------------------- 3 files changed, 45 insertions(+), 31 deletions(-) create mode 100644 .clangd create mode 100644 .my_pygame_test/main.py diff --git a/.clangd b/.clangd new file mode 100644 index 0000000000..e4a68e17b5 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + CompilationDatabase: ./.mesonpy-build/ diff --git a/.my_pygame_test/main.py b/.my_pygame_test/main.py new file mode 100644 index 0000000000..00983c4adf --- /dev/null +++ b/.my_pygame_test/main.py @@ -0,0 +1,27 @@ +import pygame +from pygame.geometry import Line + +pygame.init() + +window = pygame.display.set_mode((600, 600)) + +l = Line(10, 10, 200, 200) +p = (1, 1) + +running = True +while running: + + window.fill((255, 255, 255)) + + pygame.draw.line(window, (255, 0, 0), l.a, l.b, 5) + + pygame.draw.circle(window, (0, 255, 0), p, 5) + pygame.draw.circle(window, (0, 255, 0), l.project(p, do_clamp=True), 5) + + pygame.display.flip() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + +pygame.quit() diff --git a/src_c/line.c b/src_c/line.c index 066a0b2aad..982d236719 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -1,5 +1,7 @@ #include "doc/geometry_doc.h" #include "geometry_common.h" +#include "methodobject.h" +#include "pytypedefs.h" static double pgLine_Length(pgLineBase *line) @@ -179,27 +181,6 @@ _line_scale_helper(pgLineBase *line, double factor, double origin) return 1; } -void -_normalize_vector(double *vector) -{ - double length = sqrt(vector[0] * vector[0] + vector[1] * vector[1]); - // check to see if the vector is zero - if (length == 0) { - vector[0] = 0; - vector[1] = 0; - } - else { - vector[0] /= length; - vector[1] /= length; - } -} - -double -_length_of_vector(double *vector) -{ - return sqrt(vector[0] * vector[0] + vector[1] * vector[1]); -} - static PyObject * _line_project_helper(pgLineBase *line, double *point, int clamp) { @@ -281,21 +262,25 @@ pg_line_scale_ip(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) } static PyObject * -pg_line_project(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs, - PyObject *kwnames) +pg_line_project(pgLineObject *self, PyObject *args, PyObject *kwnames) { double point[2] = {0.f, 0.f}; int clamp = 0; - if (nargs >= 1) { - if (!pg_TwoDoublesFromObj(args[0], &point[0], &point[1])) { - return RAISE(PyExc_TypeError, - "project requires a sequence of two numbers"); - } + PyObject *point_obj = NULL; + + static char *kwlist[] = {"point", "clamp", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwnames, "O|p:project", kwlist, + &point_obj, &clamp)) { + return RAISE( + PyExc_TypeError, + "project requires a sequence(point) and an optional clamp flag"); } - if (kwnames != NULL) { - clamp = PyObject_IsTrue(args[nargs]); + if (!pg_TwoDoublesFromObj(point_obj, &point[0], &point[1])) { + return RAISE(PyExc_TypeError, + "project requires a sequence of two numbers"); } PyObject *projected_point; @@ -319,7 +304,7 @@ static struct PyMethodDef pg_line_methods[] = { {"scale", (PyCFunction)pg_line_scale, METH_FASTCALL, DOC_LINE_SCALE}, {"scale_ip", (PyCFunction)pg_line_scale_ip, METH_FASTCALL, DOC_LINE_SCALEIP}, - {"project", (PyCFunction)pg_line_project, METH_FASTCALL | METH_KEYWORDS, + {"project", (PyCFunction)pg_line_project, METH_VARARGS | METH_KEYWORDS, DOC_LINE_PROJECT}, {NULL, NULL, 0, NULL}}; From 383741f535455ddfdf6424968b6ac4934b8cce1b Mon Sep 17 00:00:00 2001 From: XFajk Date: Tue, 29 Apr 2025 20:12:15 +0200 Subject: [PATCH 13/16] added proper error handling and and reflected that in the docs and tests --- .my_pygame_test/main.py | 3 +- docs/reST/ref/geometry.rst | 2 + src_c/line.c | 104 ++++++++++++++++++++----------------- test/geometry_test.py | 11 ++++ 4 files changed, 71 insertions(+), 49 deletions(-) diff --git a/.my_pygame_test/main.py b/.my_pygame_test/main.py index 00983c4adf..f49ae66c07 100644 --- a/.my_pygame_test/main.py +++ b/.my_pygame_test/main.py @@ -10,11 +10,10 @@ running = True while running: - window.fill((255, 255, 255)) pygame.draw.line(window, (255, 0, 0), l.a, l.b, 5) - + pygame.draw.circle(window, (0, 255, 0), p, 5) pygame.draw.circle(window, (0, 255, 0), l.project(p, do_clamp=True), 5) diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst index a9c91795d3..032f01dcdb 100644 --- a/docs/reST/ref/geometry.rst +++ b/docs/reST/ref/geometry.rst @@ -762,6 +762,8 @@ Example of what the clamp argument changes. If it is `True`, the point is bounded between the line segment ends. + WARNING: This method has to have some length or the clamp parameter must be true for it to work and not throw a `ValueError` + .. versionadded:: 2.5.4 .. ## Line.project ## diff --git a/src_c/line.c b/src_c/line.c index 982d236719..0380c3ac26 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -1,6 +1,7 @@ #include "doc/geometry_doc.h" #include "geometry_common.h" #include "methodobject.h" +#include "pyerrors.h" #include "pytypedefs.h" static double @@ -181,46 +182,6 @@ _line_scale_helper(pgLineBase *line, double factor, double origin) return 1; } -static PyObject * -_line_project_helper(pgLineBase *line, double *point, int clamp) -{ - // this is a vector that goes from one point of the line to another - double line_vector[2] = {line->bx - line->ax, line->by - line->ay}; - - // this is a vector that goes from the start of the line to the point we - // are projecting onto the line - double vector_from_line_start_to_point[2] = {point[0] - line->ax, - point[1] - line->ay}; - - double dot_product = - (vector_from_line_start_to_point[0] * line_vector[0] + - vector_from_line_start_to_point[1] * line_vector[1]) / - (line_vector[0] * line_vector[0] + line_vector[1] * line_vector[1]); - - double projection[2] = {dot_product * line_vector[0], - dot_product * line_vector[1]}; - - if (clamp) { - if (dot_product < 0) { - projection[0] = 0; - projection[1] = 0; - } - else if (projection[0] * projection[0] + - projection[1] * projection[1] > - line_vector[0] * line_vector[0] + - line_vector[1] * line_vector[1]) { - projection[0] = line_vector[0]; - projection[1] = line_vector[1]; - } - } - - double projected_point[2] = {line->ax + projection[0], - line->ay + projection[1]}; - - return pg_tuple_couple_from_values_double(projected_point[0], - projected_point[1]); -} - static PyObject * pg_line_scale(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) { @@ -261,6 +222,61 @@ pg_line_scale_ip(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) Py_RETURN_NONE; } +static PyObject * +_line_project_helper(pgLineBase *line, double *point, int clamp) +{ + // this is a vector that goes from one point of the line to another + double line_vector[2] = {line->bx - line->ax, line->by - line->ay}; + double squred_line_length = + line_vector[0] * line_vector[0] + line_vector[1] * line_vector[1]; + + if (squred_line_length == 0.0 && clamp) { + double projected_point[2]; + projected_point[0] = line->ax; + projected_point[1] = line->ay; + return pg_tuple_couple_from_values_double(projected_point[0], + projected_point[1]); + } + else if (squred_line_length == 0.0) { + return RAISE(PyExc_ValueError, + "The Line has to have some length or this method has to " + "be clamped to work"); + } + + // this is a vector that goes from the start of the line to the point we + // are projecting onto the line + double vector_from_line_start_to_point[2] = {point[0] - line->ax, + point[1] - line->ay}; + + double dot_product = + (vector_from_line_start_to_point[0] * line_vector[0] + + vector_from_line_start_to_point[1] * line_vector[1]) / + (line_vector[0] * line_vector[0] + line_vector[1] * line_vector[1]); + + double projection[2] = {dot_product * line_vector[0], + dot_product * line_vector[1]}; + + if (clamp) { + if (dot_product < 0) { + projection[0] = 0; + projection[1] = 0; + } + else if (projection[0] * projection[0] + + projection[1] * projection[1] > + line_vector[0] * line_vector[0] + + line_vector[1] * line_vector[1]) { + projection[0] = line_vector[0]; + projection[1] = line_vector[1]; + } + } + + double projected_point[2] = {line->ax + projection[0], + line->ay + projection[1]}; + + return pg_tuple_couple_from_values_double(projected_point[0], + projected_point[1]); +} + static PyObject * pg_line_project(pgLineObject *self, PyObject *args, PyObject *kwnames) { @@ -283,13 +299,7 @@ pg_line_project(pgLineObject *self, PyObject *args, PyObject *kwnames) "project requires a sequence of two numbers"); } - PyObject *projected_point; - if (!(projected_point = - _line_project_helper(&pgLine_AsLine(self), point, clamp))) { - return NULL; - } - - return projected_point; + return _line_project_helper(&pgLine_AsLine(self), point, clamp); } static struct PyMethodDef pg_line_methods[] = { diff --git a/test/geometry_test.py b/test/geometry_test.py index 6e09cd2cc1..8d9ab89b08 100644 --- a/test/geometry_test.py +++ b/test/geometry_test.py @@ -2208,6 +2208,9 @@ def test_meth_project(self): test_clamp_point2 = (-50, -150) test_clamp_point3 = (-200, -200) + bad_line = Line(0, 0, 0, 0) + test_bad_line_point = (10, 10) + projected_point = line.project(test_point1) self.assertEqual(math.ceil(projected_point[0]), 50) self.assertEqual(math.ceil(projected_point[1]), 50) @@ -2224,6 +2227,14 @@ def test_meth_project(self): self.assertEqual(math.ceil(projected_point[0]), 0) self.assertEqual(math.ceil(projected_point[1]), 0) + projected_point = bad_line.project(test_bad_line_point, clamp=True) + self.assertEqual(math.ceil(projected_point[0]), 0) + self.assertEqual(math.ceil(projected_point[1]), 0) + + # testing if the method fails when it should + with self.assertRaises(ValueError): + bad_line.project(test_bad_line_point) + def test__str__(self): """Checks whether the __str__ method works correctly.""" l_str = "Line((10.1, 10.2), (4.3, 56.4))" From f7178f6b7af263232f6ac329b31fe5d28a7355c9 Mon Sep 17 00:00:00 2001 From: XFajk Date: Tue, 29 Apr 2025 20:47:10 +0200 Subject: [PATCH 14/16] removed one mistake made --- .my_pygame_test/main.py | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .my_pygame_test/main.py diff --git a/.my_pygame_test/main.py b/.my_pygame_test/main.py deleted file mode 100644 index f49ae66c07..0000000000 --- a/.my_pygame_test/main.py +++ /dev/null @@ -1,26 +0,0 @@ -import pygame -from pygame.geometry import Line - -pygame.init() - -window = pygame.display.set_mode((600, 600)) - -l = Line(10, 10, 200, 200) -p = (1, 1) - -running = True -while running: - window.fill((255, 255, 255)) - - pygame.draw.line(window, (255, 0, 0), l.a, l.b, 5) - - pygame.draw.circle(window, (0, 255, 0), p, 5) - pygame.draw.circle(window, (0, 255, 0), l.project(p, do_clamp=True), 5) - - pygame.display.flip() - - for event in pygame.event.get(): - if event.type == pygame.QUIT: - running = False - -pygame.quit() From e89deaae8a5a9c1910986a0dc6d49df42aeacc82 Mon Sep 17 00:00:00 2001 From: Rudolf Vrbensky <89221002+XFajk@users.noreply.github.com> Date: Tue, 29 Apr 2025 21:28:46 +0200 Subject: [PATCH 15/16] Update line.c --- src_c/line.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src_c/line.c b/src_c/line.c index 0380c3ac26..93a0d7e3d6 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -1,8 +1,6 @@ #include "doc/geometry_doc.h" #include "geometry_common.h" #include "methodobject.h" -#include "pyerrors.h" -#include "pytypedefs.h" static double pgLine_Length(pgLineBase *line) From d06b3835bbafcbb3b9ae72441549d15112fbd735 Mon Sep 17 00:00:00 2001 From: Rudolf Vrbensky <89221002+XFajk@users.noreply.github.com> Date: Tue, 29 Apr 2025 21:29:29 +0200 Subject: [PATCH 16/16] Update line.c --- src_c/line.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src_c/line.c b/src_c/line.c index 93a0d7e3d6..f03c5874b6 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -1,6 +1,5 @@ #include "doc/geometry_doc.h" #include "geometry_common.h" -#include "methodobject.h" static double pgLine_Length(pgLineBase *line)