Skip to content

Commit 383741f

Browse files
committed
added proper error handling and and reflected that in the docs and tests
1 parent e6c1f8c commit 383741f

File tree

4 files changed

+71
-49
lines changed

4 files changed

+71
-49
lines changed

.my_pygame_test/main.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010

1111
running = True
1212
while running:
13-
1413
window.fill((255, 255, 255))
1514

1615
pygame.draw.line(window, (255, 0, 0), l.a, l.b, 5)
17-
16+
1817
pygame.draw.circle(window, (0, 255, 0), p, 5)
1918
pygame.draw.circle(window, (0, 255, 0), l.project(p, do_clamp=True), 5)
2019

docs/reST/ref/geometry.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,8 @@
762762

763763
Example of what the clamp argument changes. If it is `True`, the point is bounded between the line segment ends.
764764

765+
WARNING: This method has to have some length or the clamp parameter must be true for it to work and not throw a `ValueError`
766+
765767
.. versionadded:: 2.5.4
766768

767769
.. ## Line.project ##

src_c/line.c

Lines changed: 57 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "doc/geometry_doc.h"
22
#include "geometry_common.h"
33
#include "methodobject.h"
4+
#include "pyerrors.h"
45
#include "pytypedefs.h"
56

67
static double
@@ -181,46 +182,6 @@ _line_scale_helper(pgLineBase *line, double factor, double origin)
181182
return 1;
182183
}
183184

184-
static PyObject *
185-
_line_project_helper(pgLineBase *line, double *point, int clamp)
186-
{
187-
// this is a vector that goes from one point of the line to another
188-
double line_vector[2] = {line->bx - line->ax, line->by - line->ay};
189-
190-
// this is a vector that goes from the start of the line to the point we
191-
// are projecting onto the line
192-
double vector_from_line_start_to_point[2] = {point[0] - line->ax,
193-
point[1] - line->ay};
194-
195-
double dot_product =
196-
(vector_from_line_start_to_point[0] * line_vector[0] +
197-
vector_from_line_start_to_point[1] * line_vector[1]) /
198-
(line_vector[0] * line_vector[0] + line_vector[1] * line_vector[1]);
199-
200-
double projection[2] = {dot_product * line_vector[0],
201-
dot_product * line_vector[1]};
202-
203-
if (clamp) {
204-
if (dot_product < 0) {
205-
projection[0] = 0;
206-
projection[1] = 0;
207-
}
208-
else if (projection[0] * projection[0] +
209-
projection[1] * projection[1] >
210-
line_vector[0] * line_vector[0] +
211-
line_vector[1] * line_vector[1]) {
212-
projection[0] = line_vector[0];
213-
projection[1] = line_vector[1];
214-
}
215-
}
216-
217-
double projected_point[2] = {line->ax + projection[0],
218-
line->ay + projection[1]};
219-
220-
return pg_tuple_couple_from_values_double(projected_point[0],
221-
projected_point[1]);
222-
}
223-
224185
static PyObject *
225186
pg_line_scale(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs)
226187
{
@@ -261,6 +222,61 @@ pg_line_scale_ip(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs)
261222
Py_RETURN_NONE;
262223
}
263224

225+
static PyObject *
226+
_line_project_helper(pgLineBase *line, double *point, int clamp)
227+
{
228+
// this is a vector that goes from one point of the line to another
229+
double line_vector[2] = {line->bx - line->ax, line->by - line->ay};
230+
double squred_line_length =
231+
line_vector[0] * line_vector[0] + line_vector[1] * line_vector[1];
232+
233+
if (squred_line_length == 0.0 && clamp) {
234+
double projected_point[2];
235+
projected_point[0] = line->ax;
236+
projected_point[1] = line->ay;
237+
return pg_tuple_couple_from_values_double(projected_point[0],
238+
projected_point[1]);
239+
}
240+
else if (squred_line_length == 0.0) {
241+
return RAISE(PyExc_ValueError,
242+
"The Line has to have some length or this method has to "
243+
"be clamped to work");
244+
}
245+
246+
// this is a vector that goes from the start of the line to the point we
247+
// are projecting onto the line
248+
double vector_from_line_start_to_point[2] = {point[0] - line->ax,
249+
point[1] - line->ay};
250+
251+
double dot_product =
252+
(vector_from_line_start_to_point[0] * line_vector[0] +
253+
vector_from_line_start_to_point[1] * line_vector[1]) /
254+
(line_vector[0] * line_vector[0] + line_vector[1] * line_vector[1]);
255+
256+
double projection[2] = {dot_product * line_vector[0],
257+
dot_product * line_vector[1]};
258+
259+
if (clamp) {
260+
if (dot_product < 0) {
261+
projection[0] = 0;
262+
projection[1] = 0;
263+
}
264+
else if (projection[0] * projection[0] +
265+
projection[1] * projection[1] >
266+
line_vector[0] * line_vector[0] +
267+
line_vector[1] * line_vector[1]) {
268+
projection[0] = line_vector[0];
269+
projection[1] = line_vector[1];
270+
}
271+
}
272+
273+
double projected_point[2] = {line->ax + projection[0],
274+
line->ay + projection[1]};
275+
276+
return pg_tuple_couple_from_values_double(projected_point[0],
277+
projected_point[1]);
278+
}
279+
264280
static PyObject *
265281
pg_line_project(pgLineObject *self, PyObject *args, PyObject *kwnames)
266282
{
@@ -283,13 +299,7 @@ pg_line_project(pgLineObject *self, PyObject *args, PyObject *kwnames)
283299
"project requires a sequence of two numbers");
284300
}
285301

286-
PyObject *projected_point;
287-
if (!(projected_point =
288-
_line_project_helper(&pgLine_AsLine(self), point, clamp))) {
289-
return NULL;
290-
}
291-
292-
return projected_point;
302+
return _line_project_helper(&pgLine_AsLine(self), point, clamp);
293303
}
294304

295305
static struct PyMethodDef pg_line_methods[] = {

test/geometry_test.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2208,6 +2208,9 @@ def test_meth_project(self):
22082208
test_clamp_point2 = (-50, -150)
22092209
test_clamp_point3 = (-200, -200)
22102210

2211+
bad_line = Line(0, 0, 0, 0)
2212+
test_bad_line_point = (10, 10)
2213+
22112214
projected_point = line.project(test_point1)
22122215
self.assertEqual(math.ceil(projected_point[0]), 50)
22132216
self.assertEqual(math.ceil(projected_point[1]), 50)
@@ -2224,6 +2227,14 @@ def test_meth_project(self):
22242227
self.assertEqual(math.ceil(projected_point[0]), 0)
22252228
self.assertEqual(math.ceil(projected_point[1]), 0)
22262229

2230+
projected_point = bad_line.project(test_bad_line_point, clamp=True)
2231+
self.assertEqual(math.ceil(projected_point[0]), 0)
2232+
self.assertEqual(math.ceil(projected_point[1]), 0)
2233+
2234+
# testing if the method fails when it should
2235+
with self.assertRaises(ValueError):
2236+
bad_line.project(test_bad_line_point)
2237+
22272238
def test__str__(self):
22282239
"""Checks whether the __str__ method works correctly."""
22292240
l_str = "Line((10.1, 10.2), (4.3, 56.4))"

0 commit comments

Comments
 (0)