Skip to content

Commit 41ecf29

Browse files
committed
base
1 parent 5fbff8f commit 41ecf29

File tree

5 files changed

+324
-1
lines changed

5 files changed

+324
-1
lines changed

docs/line.rst

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,4 +335,39 @@ Line Methods
335335
336336
Returns a new `Line` having the same position and radius as the original.
337337

338-
.. ## Line.copy ##
338+
.. ## Line.copy ##
339+
340+
.. method:: as_segments
341+
342+
| :sl:`returns the line as a list of segments`
343+
| :sg:`as_segments(n_segments) -> [(x, y), (x, y)]`
344+
345+
Segments the original line into N Lines of equal length and returns a list of
346+
them. The number of segments is determined by the `n_segments` parameter.
347+
348+
.. note::
349+
The original line is not modified. The returned list of lines will always
350+
have the first line's `a` point at the same position as the original line's `a`
351+
point and the last line's `b` point at the same position as the original line's
352+
`b` point.
353+
354+
355+
.. ## Line.as_segments ##
356+
357+
.. method:: as_points
358+
359+
| :sl:`returns the line as a list of points`
360+
| :sg:`as_points(n_points) -> [(x, y), (x, y)]`
361+
362+
Returns a list of points that represent the line. The first point in the list
363+
will be the line's `a` point and the last point will be the line's `b` point.
364+
The number of points is determined by the `n_points` parameter.
365+
366+
.. note::
367+
The n_points parameter refers to the number of points that will created in
368+
between the line's `a` and `b` points. The number of points returned will
369+
always be n_points + 2. Because of this the n_points parameter must be at
370+
least 0.
371+
372+
373+
.. ## Line.as_points ##

examples/line_as_points.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import pygame
2+
from geometry import *
3+
from pygame.draw import circle as draw_circle, line as draw_line
4+
5+
WIDTH, HEIGHT = 800, 600
6+
WIDTH2, HEIGHT2 = WIDTH / 2, HEIGHT / 2
7+
pygame.init()
8+
screen = pygame.display.set_mode((WIDTH, HEIGHT))
9+
clock = pygame.time.Clock()
10+
keep = True
11+
N = 50
12+
# Create a line
13+
line = Line((WIDTH / 4, HEIGHT2), (3 * WIDTH / 4, HEIGHT2))
14+
15+
while keep:
16+
screen.fill((30, 30, 30))
17+
# Draw the line
18+
# draw_line(screen, (255, 255, 255), line.a, line.b, 2)
19+
# Draw the points
20+
for i, segment in enumerate(line.as_segments(N)):
21+
draw_line(
22+
screen,
23+
((100 + 45 * (i + 1)) % 255, 15 * (i + 1) % 255, (135 * (i + 1)) % 255),
24+
segment.a,
25+
segment.b,
26+
3,
27+
)
28+
for point in line.as_points(N - 1):
29+
draw_circle(screen, (255, 255, 255), point, 1)
30+
31+
line.b = pygame.mouse.get_pos()
32+
33+
pygame.display.flip()
34+
clock.tick(165)
35+
for event in pygame.event.get():
36+
if event.type == pygame.QUIT:
37+
keep = False
38+
if event.type == pygame.MOUSEWHEEL:
39+
if event.y > 0:
40+
N += 1
41+
else:
42+
if N > 2:
43+
N -= 1

geometry.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ class Line(Sequence[float]):
123123
def flip_ip(self) -> None: ...
124124
def is_parallel(self, line: LineValue) -> bool: ...
125125
def is_perpendicular(self, line: LineValue) -> bool: ...
126+
def as_points(self, n_points: int) -> List[Tuple[float, float]]: ...
127+
def as_segments(self, n_segments: int) -> List[Tuple[Line]]: ...
126128

127129
class Circle:
128130
x: float

src_c/line.c

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,129 @@ pg_line_flip_ip(pgLineObject *self, PyObject *_null)
490490
Py_RETURN_NONE;
491491
}
492492

493+
static PyObject *
494+
pg_line_as_points(pgLineObject *self, PyObject *arg)
495+
{
496+
int N = 0;
497+
if (!pg_IntFromObj(arg, &N)) {
498+
return RAISE(PyExc_TypeError, "as_points requires an integer");
499+
}
500+
if (N < 0) {
501+
return RAISE(PyExc_ValueError, "as_points requires a positive integer");
502+
}
503+
504+
PyObject *point = NULL;
505+
PyObject *list = PyList_New(2 + N);
506+
if (!list) {
507+
return NULL;
508+
}
509+
510+
pgLineBase *line = &self->line;
511+
512+
// Add the start and end points to the list
513+
point = pg_TupleFromDoublePair(line->x1, line->y1);
514+
if (!point) {
515+
Py_DECREF(list);
516+
return NULL;
517+
}
518+
PyList_SET_ITEM(list, 0, point);
519+
point = pg_TupleFromDoublePair(line->x2, line->y2);
520+
if (!point) {
521+
Py_DECREF(list);
522+
return NULL;
523+
}
524+
PyList_SET_ITEM(list, N + 1, point);
525+
526+
if (!N) {
527+
return list;
528+
}
529+
else if (N == 1) {
530+
point = pg_TupleFromDoublePair((line->x1 + line->x2) / 2,
531+
(line->y1 + line->y2) / 2);
532+
if (!point) {
533+
Py_DECREF(list);
534+
return NULL;
535+
}
536+
PyList_SET_ITEM(list, 1, point);
537+
return list;
538+
}
539+
540+
double step_x = (line->x2 - line->x1) / (N + 1);
541+
double step_y = (line->y2 - line->y1) / (N + 1);
542+
double x = line->x1 + step_x;
543+
double y = line->y1 + step_y;
544+
545+
Py_ssize_t i;
546+
for (i = 1; i < N + 1; i++) {
547+
point = pg_TupleFromDoublePair(x, y);
548+
if (!point) {
549+
Py_DECREF(list);
550+
return NULL;
551+
}
552+
PyList_SET_ITEM(list, i, point);
553+
x += step_x;
554+
y += step_y;
555+
}
556+
557+
return list;
558+
}
559+
560+
static PyObject *
561+
pg_line_as_segments(pgLineObject *self, PyObject *arg)
562+
{
563+
/* Segments the line into N Lines of equal length and returns a list of
564+
* them. */
565+
566+
int N = 1;
567+
if (!pg_IntFromObj(arg, &N)) {
568+
return RAISE(PyExc_TypeError,
569+
"as_segments requires an integer");
570+
}
571+
if (N < 1) {
572+
return RAISE(PyExc_ValueError,
573+
"as_segments requires a positive integer");
574+
}
575+
576+
PyObject *line_obj = NULL;
577+
PyObject *list = PyList_New(N);
578+
if (!list) {
579+
return NULL;
580+
}
581+
582+
if (N == 1) {
583+
line_obj = pg_line_copy(self, NULL);
584+
if (!line_obj) {
585+
Py_DECREF(list);
586+
return NULL;
587+
}
588+
PyList_SET_ITEM(list, 0, line_obj);
589+
return list;
590+
}
591+
592+
pgLineBase *line = &self->line;
593+
594+
double step_x = (line->x2 - line->x1) / N;
595+
double step_y = (line->y2 - line->y1) / N;
596+
double x1 = line->x1, y1 = line->y1;
597+
double x2 = x1 + step_x, y2 = y1 + step_y;
598+
599+
Py_ssize_t i;
600+
for (i = 0; i < N; i++) {
601+
line_obj = _pg_line_subtype_new4(Py_TYPE(self), x1, y1, x2, y2);
602+
if (!line_obj) {
603+
Py_DECREF(list);
604+
return NULL;
605+
}
606+
PyList_SET_ITEM(list, i, line_obj);
607+
x1 = x2;
608+
y1 = y2;
609+
x2 += step_x;
610+
y2 += step_y;
611+
}
612+
613+
return list;
614+
}
615+
493616
static struct PyMethodDef pg_line_methods[] = {
494617
{"__copy__", (PyCFunction)pg_line_copy, METH_NOARGS, NULL},
495618
{"copy", (PyCFunction)pg_line_copy, METH_NOARGS, NULL},
@@ -508,6 +631,8 @@ static struct PyMethodDef pg_line_methods[] = {
508631
{"at", (PyCFunction)pg_line_at, METH_O, NULL},
509632
{"flip", (PyCFunction)pg_line_flip, METH_NOARGS, NULL},
510633
{"flip_ip", (PyCFunction)pg_line_flip_ip, METH_NOARGS, NULL},
634+
{"as_points", (PyCFunction)pg_line_as_points, METH_O, NULL},
635+
{"as_segments", (PyCFunction)pg_line_as_segments, METH_O, NULL},
511636
{NULL, NULL, 0, NULL}};
512637

513638
/* sequence functions */

test/test_line.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
from math import sqrt
23

34
from pygame import Vector2, Vector3, Rect
45

@@ -8,6 +9,17 @@
89
E_F = "Expected False, "
910

1011

12+
def _distance(p1, p2):
13+
return sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
14+
15+
16+
def get_points_between(line, n_pts):
17+
dx = (line.x2 - line.x1) / (n_pts + 1)
18+
dy = (line.y2 - line.y1) / (n_pts + 1)
19+
20+
return [(line.x1 + i * dx, line.y1 + i * dy) for i in range(n_pts + 2)]
21+
22+
1123
class LineTypeTest(unittest.TestCase):
1224
class ClassWithLineAttrib:
1325
def __init__(self, line):
@@ -1004,6 +1016,112 @@ def test_meth_perpendicular_argtype(self):
10041016
with self.assertRaises(TypeError):
10051017
l.is_perpendicular(value)
10061018

1019+
def test_meth_as_points(self):
1020+
"""Test the as_points method."""
1021+
l = Line(0, 0, 1, 1)
1022+
1023+
for i in range(100):
1024+
pts = l.as_points(i)
1025+
self.assertEqual(2 + i, len(pts))
1026+
ex_pts = get_points_between(l, i)
1027+
1028+
for actual_pt, expected_pt in zip(pts, ex_pts):
1029+
self.assertIsInstance(actual_pt, tuple)
1030+
self.assertEqual(2, len(actual_pt))
1031+
self.assertAlmostEqual(expected_pt[0], actual_pt[0], places=14)
1032+
self.assertAlmostEqual(expected_pt[1], actual_pt[1], places=14)
1033+
1034+
self.assertAlmostEqual(
1035+
sum(_distance(p1, p2) for p1, p2 in zip(pts, pts[1:])), l.length
1036+
)
1037+
1038+
def test_meth_as_points_argtype(self):
1039+
l = Line(0, 0, 1, 1)
1040+
args = [
1041+
"string",
1042+
None,
1043+
[1, 2, 3],
1044+
[1, "s", 3, 4],
1045+
(1, 2, 3),
1046+
(1, "s", 3, 4),
1047+
((1, "s"), (3, 4)),
1048+
((1, 4), (3, "4")),
1049+
{1: 2, 3: 4},
1050+
object(),
1051+
]
1052+
for value in args:
1053+
with self.assertRaises(TypeError):
1054+
l.as_points(value)
1055+
1056+
def test_meth_as_points_argnum(self):
1057+
l = Line(0, 0, 1, 1)
1058+
args = [
1059+
(1, 2),
1060+
(1, 2, 3),
1061+
(1, 2, 3, 4),
1062+
(1, 2, 3, 4, 5),
1063+
]
1064+
for value in args:
1065+
with self.assertRaises(TypeError):
1066+
l.as_points(*value)
1067+
1068+
def test_meth_as_points_argvalue(self):
1069+
l = Line(0, 0, 1, 1)
1070+
args = [
1071+
-1,
1072+
-2,
1073+
-3,
1074+
-4,
1075+
-5,
1076+
]
1077+
for value in args:
1078+
with self.assertRaises(ValueError):
1079+
l.as_points(value)
1080+
1081+
def test_meth_as_segments_argtype(self):
1082+
l = Line(0, 0, 1, 1)
1083+
args = [
1084+
"string",
1085+
None,
1086+
[1, 2, 3],
1087+
[1, "s", 3, 4],
1088+
(1, 2, 3),
1089+
(1, "s", 3, 4),
1090+
((1, "s"), (3, 4)),
1091+
((1, 4), (3, "4")),
1092+
{1: 2, 3: 4},
1093+
object(),
1094+
]
1095+
for value in args:
1096+
with self.assertRaises(TypeError):
1097+
l.as_segments(value)
1098+
1099+
def test_meth_as_segments_argnum(self):
1100+
l = Line(0, 0, 1, 1)
1101+
args = [
1102+
(1, 2),
1103+
(1, 2, 3),
1104+
(1, 2, 3, 4),
1105+
(1, 2, 3, 4, 5),
1106+
]
1107+
for value in args:
1108+
with self.assertRaises(TypeError):
1109+
l.as_segments(*value)
1110+
1111+
def test_meth_as_segments_argvalue(self):
1112+
l = Line(0, 0, 1, 1)
1113+
args = [
1114+
0,
1115+
-1,
1116+
-2,
1117+
-3,
1118+
-4,
1119+
-5,
1120+
]
1121+
for value in args:
1122+
with self.assertRaises(ValueError):
1123+
l.as_segments(value)
1124+
10071125

10081126
if __name__ == "__main__":
10091127
unittest.main()

0 commit comments

Comments
 (0)