@@ -179,6 +179,70 @@ _line_scale_helper(pgLineBase *line, double factor, double origin)
179179 return 1 ;
180180}
181181
182+ void
183+ _normalize_vector (double * vector )
184+ {
185+ double length = sqrt (vector [0 ] * vector [0 ] + vector [1 ] * vector [1 ]);
186+ // check to see if the vector is zero
187+ if (length == 0 ) {
188+ vector [0 ] = 0 ;
189+ vector [1 ] = 0 ;
190+ }
191+ else {
192+ vector [0 ] /= length ;
193+ vector [1 ] /= length ;
194+ }
195+ }
196+
197+ double
198+ _length_of_vector (double * vector )
199+ {
200+ return sqrt (vector [0 ] * vector [0 ] + vector [1 ] * vector [1 ]);
201+ }
202+
203+ static PyObject *
204+ _line_project_helper (pgLineBase * line , double * point , int do_clamp )
205+ {
206+ // this is a vector that goes from one point of the line to another
207+ double line_vector [2 ] = {line -> bx - line -> ax , line -> by - line -> ay };
208+ double line_length = _length_of_vector (line_vector );
209+
210+ // this is a unit vector that points in the direction of the line
211+ double normalized_line_vector [2 ] = {line_vector [0 ], line_vector [1 ]};
212+ _normalize_vector (normalized_line_vector );
213+
214+ // this is a vector that goes from the start of the line to the point we
215+ // are projecting onto the line
216+ double vector_from_line_start_to_point [2 ] = {point [0 ] - line -> ax ,
217+ point [1 ] - line -> ay };
218+
219+ double dot_product =
220+ vector_from_line_start_to_point [0 ] * normalized_line_vector [0 ] +
221+ vector_from_line_start_to_point [1 ] * normalized_line_vector [1 ];
222+
223+ double projection [2 ] = {dot_product * normalized_line_vector [0 ],
224+ dot_product * normalized_line_vector [1 ]};
225+
226+ if (do_clamp ) {
227+ if (dot_product > line_length ) {
228+ projection [0 ] = line_vector [0 ];
229+ projection [1 ] = line_vector [1 ];
230+ }
231+ else if (dot_product < 0 ) {
232+ projection [0 ] = 0 ;
233+ projection [1 ] = 0 ;
234+ }
235+ }
236+
237+ double projected_point [2 ] = {line -> ax + projection [0 ],
238+ line -> ay + projection [1 ]};
239+
240+ PyObject * projected_tuple =
241+ Py_BuildValue ("(dd)" , projected_point [0 ], projected_point [1 ]);
242+
243+ return projected_tuple ;
244+ }
245+
182246static PyObject *
183247pg_line_scale (pgLineObject * self , PyObject * const * args , Py_ssize_t nargs )
184248{
@@ -219,6 +283,54 @@ pg_line_scale_ip(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs)
219283 Py_RETURN_NONE ;
220284}
221285
286+ static PyObject *
287+ pg_line_project (pgLineObject * self , PyObject * args , PyObject * kwnames )
288+ {
289+ PyObject * point_obj = NULL ;
290+ int do_clamp = 0 ;
291+
292+ static char * kwlist [] = {"point" , "do_clamp" , NULL };
293+
294+ if (!PyArg_ParseTupleAndKeywords (args , kwnames , "O|p:project" , kwlist ,
295+ & point_obj , & do_clamp )) {
296+ return RAISE (
297+ PyExc_TypeError ,
298+ "project requires a sequence(point) and an optional clamp flag" );
299+ }
300+
301+ PyObject * item ;
302+ double point [2 ];
303+
304+ if (!PySequence_Check (point_obj ) || PySequence_Size (point_obj ) != 2 ) {
305+ return RAISE (
306+ PyExc_ValueError ,
307+ "project requires the point to be a sequence of 2 elements" );
308+ }
309+
310+ item = PySequence_GetItem (point_obj , 0 );
311+ point [0 ] = PyFloat_AsDouble (item );
312+
313+ PyObject * error_type ;
314+ if ((error_type = PyErr_Occurred ())) {
315+ return NULL ;
316+ }
317+
318+ item = PySequence_GetItem (point_obj , 1 );
319+ point [1 ] = PyFloat_AsDouble (item );
320+
321+ if ((error_type = PyErr_Occurred ())) {
322+ return NULL ;
323+ }
324+
325+ PyObject * projected_point ;
326+ if (!(projected_point =
327+ _line_project_helper (& pgLine_AsLine (self ), point , do_clamp ))) {
328+ return NULL ;
329+ }
330+
331+ return projected_point ;
332+ }
333+
222334static struct PyMethodDef pg_line_methods [] = {
223335 {"__copy__" , (PyCFunction )pg_line_copy , METH_NOARGS , DOC_LINE_COPY },
224336 {"copy" , (PyCFunction )pg_line_copy , METH_NOARGS , DOC_LINE_COPY },
@@ -231,6 +343,8 @@ static struct PyMethodDef pg_line_methods[] = {
231343 {"scale" , (PyCFunction )pg_line_scale , METH_FASTCALL , DOC_LINE_SCALE },
232344 {"scale_ip" , (PyCFunction )pg_line_scale_ip , METH_FASTCALL ,
233345 DOC_LINE_SCALEIP },
346+ {"project" , (PyCFunction )pg_line_project , METH_VARARGS | METH_KEYWORDS ,
347+ "" },
234348 {NULL , NULL , 0 , NULL }};
235349
236350static PyObject *
0 commit comments