diff --git a/docs/geometry.rst b/docs/geometry.rst index 505a1a78..74b612f9 100644 --- a/docs/geometry.rst +++ b/docs/geometry.rst @@ -126,6 +126,8 @@ other objects. colliderect: Checks if the line collides with the given rectangle. + collidepolygon: Checks if the line collides with the given polygon. + collideswith: Checks if the line collides with the given object. as_circle: Returns a circle which fully encloses the line. @@ -174,6 +176,8 @@ other objects. collidepoint: Checks if the polygon collides with the given point. + collideline: Checks if the polygon collides with the given line. + collidecircle: Checks if the polygon collides with the given circle. insert_vertex: Adds a vertex to the polygon. diff --git a/docs/line.rst b/docs/line.rst index ec56d38f..91f54d9d 100644 --- a/docs/line.rst +++ b/docs/line.rst @@ -320,6 +320,29 @@ Line Methods .. ## Line.collidecircle ## + .. method:: collidepolygon + + | :sl:`test if a line intersects with a polygon` + | :sg:`collidepolygon(Polygon, only_edges=False) -> bool` + | :sg:`collidepolygon((x1, y1), (x2, y2), ..., only_edges=False) -> bool` + | :sg:`collidepolygon(x1, y1, x2, y2, ..., only_edges=False) -> bool` + + Tests whether a given `Polygon` collides with the `Line`. + It takes either a `Polygon` or Polygon-like object as an argument and it returns + `True` if the polygon collides with the `Line`, `False` otherwise. + + The optional `only_edges` argument can be set to `True` to only test whether the + edges of the polygon intersect the `Line`. This means that a Line that is + inscribed by the `Polygon` or completely outside of it will not be considered colliding. + This can be useful for performance reasons if you only care about the edges of the + polygon. + + .. note:: + Keep in mind that the more vertices the polygon has, the more CPU time it will + take to calculate the collision. + + .. ## Line.collidepolygon ## + .. method:: collideswith diff --git a/docs/polygon.rst b/docs/polygon.rst index a27c1745..56080308 100644 --- a/docs/polygon.rst +++ b/docs/polygon.rst @@ -157,6 +157,23 @@ Polygon Methods .. ## Polygon.collidepoint ## + .. method:: collideline + + | :sl:`tests if a line intersects the polygon` + | :sg:`collideline(Line, only_edges=False) -> bool` + | :sg:`collideline((x1, y1), (x2, y2), only_edges=False) -> bool` + | :sg:`collideline(x1, y1, x2, y2, only_edges=False) -> bool` + + Tests whether a given `Line` collides with the `Polygon`. + It takes either a `Line` or Line-like object as an argument and it returns `True` + if the `Line` collides with the `Polygon`, `False` otherwise. + + The optional `only_edges` argument can be set to `True` to only test whether the + edges of the polygon intersect the `Line`. This means that a Line that is + inscribed by the `Polygon` or completely outside of it will not be considered colliding. + + .. ## Polygon.collideline ## + .. method:: collidecircle | :sl:`tests if a circle is inside the polygon` @@ -171,6 +188,7 @@ Polygon Methods The optional `only_edges` argument can be set to `True` to only test whether the edges of the polygon intersect the `Circle`. This means that a Polygon that is completely inscribed in, or circumscribed by the `Circle` will not be considered colliding. + This can be useful for performance reasons if you only care about the edges of the polygon. diff --git a/examples/polygon_line_collision.py b/examples/polygon_line_collision.py new file mode 100644 index 00000000..5b3bcc63 --- /dev/null +++ b/examples/polygon_line_collision.py @@ -0,0 +1,108 @@ +from math import radians, sin, cos + +import pygame +from pygame.draw import line as draw_line, polygon as draw_polygon +from geometry import Line, regular_polygon + + +# using this because we're missing line.rotate(), rotates a line around its midpoint +def rotate_line(line, d_ang): + angle = radians(d_ang) + ca, sa = cos(angle), sin(angle) + xm, ym = line.midpoint + ax, ay = line.a + bx, by = line.b + ax -= xm + ay -= ym + bx -= xm + by -= ym + new_ax, new_ay = xm + ax * ca - ay * sa, ym + ax * sa + ay * ca + new_bx, new_by = xm + bx * ca - by * sa, ym + bx * sa + by * ca + line.a, line.b = (new_ax, new_ay), (new_bx, new_by) + + +pygame.init() + +WHITE = (255, 255, 255) +YELLOW = (255, 255, 0) +RED = (255, 0, 0) +SHAPE_THICKNESS = 3 +FPS = 60 +WIDTH, HEIGHT = 800, 800 +WIDTH2, HEIGHT2 = WIDTH // 2, HEIGHT // 2 + +screen = pygame.display.set_mode((WIDTH, HEIGHT)) +pygame.display.set_caption("Polygon-Line Collision Visualization") +clock = pygame.time.Clock() + +font = pygame.font.SysFont("Arial", 25, bold=True) +colliding_text = font.render("Colliding", True, RED) +colliding_textr = colliding_text.get_rect(center=(WIDTH2, 50)) +not_colliding_text = font.render("Not colliding", True, WHITE) +not_colliding_textr = not_colliding_text.get_rect(center=(WIDTH2, 50)) + +modei_text = font.render("Right click to toggle collision mode", True, WHITE) +modei_textr = modei_text.get_rect(center=(WIDTH2, HEIGHT - 50)) + +modei2_text = font.render("Left click to rotate", True, WHITE) +modei2_textr = modei2_text.get_rect(center=(WIDTH2, HEIGHT - 80)) + +mode0_txt = font.render("Current: EDGES ONLY", True, YELLOW) +mode0_txtr = mode0_txt.get_rect(center=(WIDTH2, HEIGHT - 25)) + +mode1_txt = font.render("Current: FULL", True, YELLOW) +mode1_txtr = mode1_txt.get_rect(center=(WIDTH2, HEIGHT - 25)) + +polygon = regular_polygon(6, (WIDTH2, HEIGHT2), 180) +line = Line((0, 0), (135, 135)) +only_edges = False +running = True +color = WHITE + +rotating = False + +while running: + delta = (clock.tick(FPS) / 1000) * 60 + + line.midpoint = pygame.mouse.get_pos() + + colliding = line.collidepolygon(polygon, only_edges) + # Alternative: + # colliding = polygon.collideline(line, only_edges) + + if rotating: + rotate_line(line, 2) + + color = RED if colliding else WHITE + + screen.fill((10, 10, 10)) + + draw_polygon(screen, color, polygon.vertices, SHAPE_THICKNESS) + draw_line(screen, color, line.a, line.b, SHAPE_THICKNESS) + + screen.blit( + colliding_text if colliding else not_colliding_text, + colliding_textr if colliding else not_colliding_textr, + ) + + screen.blit(modei2_text, modei2_textr) + screen.blit(modei_text, modei_textr) + + screen.blit( + mode0_txt if only_edges else mode1_txt, mode0_txtr if only_edges else mode1_txtr + ) + + pygame.display.flip() + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.MOUSEBUTTONDOWN: + if event.button == 1: + rotating = True + elif event.button == 3: + only_edges = not only_edges + elif event.type == pygame.MOUSEBUTTONUP: + if event.button == 1: + rotating = False +pygame.quit() diff --git a/geometry.pyi b/geometry.pyi index 459317c0..4eacc01f 100644 --- a/geometry.pyi +++ b/geometry.pyi @@ -111,6 +111,7 @@ class Line(Sequence[float]): def collidecircle(self, circle: CircleValue) -> bool: ... @overload def collidecircle(self, x: float, y: float, r: float) -> bool: ... + def collidepolygon(self, polygon: Polygon, only_edges: bool = False) -> bool: ... def as_circle(self) -> Circle: ... def as_rect(self) -> Rect: ... @overload @@ -243,6 +244,7 @@ class Polygon: def collidepoint(self, x: float, y: float) -> bool: ... @overload def collidepoint(self, point: Coordinate) -> bool: ... + def collideline(self, line: LineValue, only_edges: bool = False) -> bool: ... def get_bounding_box(self) -> Rect: ... def is_convex(self) -> bool: ... @overload diff --git a/src_c/collisions.c b/src_c/collisions.c index 9a79b2d4..bb8f2355 100644 --- a/src_c/collisions.c +++ b/src_c/collisions.c @@ -321,6 +321,68 @@ pgCollision_PolygonPoint(pgPolygonBase *poly, double x, double y) return collision; } +static inline int +pgCollision_PolygonPoints(pgPolygonBase *poly, double x1, double y1, double x2, + double y2) +{ + int collision1 = 0, collision2 = 0; + Py_ssize_t i, j; + + double *vertices = poly->vertices; + + for (i = 0, j = poly->verts_num - 1; i < poly->verts_num; j = i++) { + double xi = vertices[i * 2]; + double yi = vertices[i * 2 + 1]; + double xj = vertices[j * 2]; + double yj = vertices[j * 2 + 1]; + + double xj_xi = xj - xi; + double yj_yi = yj - yi; + + if (((yi > y1) != (yj > y1)) && + (x1 < xj_xi * (y1 - yi) / yj_yi + xi)) { + collision1 = !collision1; + } + + if (((yi > y2) != (yj > y2)) && + (x2 < xj_xi * (y2 - yi) / yj_yi + xi)) { + collision2 = !collision2; + } + } + + return collision1 || collision2; +} + +static inline int +_pgCollision_line_polyedges(pgLineBase *line, pgPolygonBase *poly) +{ + Py_ssize_t i, j; + double *vertices = poly->vertices; + + for (i = 0, j = poly->verts_num - 1; i < poly->verts_num; j = i++) { + if (pgCollision_LineLine( + line, &(pgLineBase){vertices[j * 2], vertices[j * 2 + 1], + vertices[i * 2], vertices[i * 2 + 1]})) { + return 1; + } + } + + return 0; +} + +static inline int +pgCollision_PolygonLine(pgPolygonBase *poly, pgLineBase *line, int only_edges) +{ + int collision = _pgCollision_line_polyedges(line, poly); + + if (collision || only_edges) { + return collision; + } + + return pgCollision_PolygonPoints(poly, line->x1, line->y1, line->x2, + line->y2); +} + static int _pgCollision_PolygonPoint_opt(pgPolygonBase *poly, double x, double y) { diff --git a/src_c/include/collisions.h b/src_c/include/collisions.h index 20ff5c5f..b3ffe1ce 100644 --- a/src_c/include/collisions.h +++ b/src_c/include/collisions.h @@ -43,7 +43,10 @@ pgRaycast_LineCircle(pgLineBase *, pgCircleBase *, double, double *); static int pgCollision_PolygonPoint(pgPolygonBase *, double, double); +static int +pgCollision_PolygonLine(pgPolygonBase *, pgLineBase *, int); static int pgCollision_CirclePolygon(pgCircleBase *, pgPolygonBase *, int); + #endif /* ~_PG_COLLISIONS_H */ diff --git a/src_c/line.c b/src_c/line.c index b8c1d0b4..163cd2ae 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -116,7 +116,7 @@ pgLine_FromObject(PyObject *obj, pgLineBase *out) } if (PySequence_Check(obj)) { length = PySequence_Length(obj); - if (length == 4) { + if (length == 4 && !pgPolygon_Check(obj)) { PyObject *tmp; tmp = PySequence_GetItem(obj, 0); if (!pg_DoubleFromObj(tmp, &(out->x1))) { @@ -169,6 +169,9 @@ pgLine_FromObject(PyObject *obj, pgLineBase *out) Py_DECREF(sub); return IS_LINE_VALID(out); } + else { + return 0; + } } if (PyObject_HasAttrString(obj, "line")) { PyObject *lineattr; @@ -692,6 +695,32 @@ pg_line_scale_ip(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) { Py_RETURN_NONE; } +static PyObject * +pg_line_collidepolygon(pgLineObject *self, PyObject *const *args, + Py_ssize_t nargs) +{ + pgPolygonBase poly; + int was_sequence, result, only_edges = 0; + + /* Check for the optional only_edges argument */ + if (PyBool_Check(args[nargs - 1])) { + only_edges = args[nargs - 1] == Py_True; + nargs--; + } + + if (!pgPolygon_FromObjectFastcall(args, nargs, &poly, &was_sequence)) { + return RAISE( + PyExc_TypeError, + "collidepolygon requires a Polygon or PolygonLike object"); + } + + result = pgCollision_PolygonLine(&poly, &self->line, only_edges); + + PG_FREEPOLY_COND(&poly, was_sequence); + + return PyBool_FromLong(result); +} + static PyObject * pg_line_as_circle(pgLineObject *self, PyObject *_null) { @@ -718,6 +747,8 @@ static struct PyMethodDef pg_line_methods[] = { {"collidecircle", (PyCFunction)pg_line_collidecircle, METH_FASTCALL, NULL}, {"colliderect", (PyCFunction)pg_line_colliderect, METH_FASTCALL, NULL}, {"collideswith", (PyCFunction)pg_line_collideswith, METH_O, NULL}, + {"collidepolygon", (PyCFunction)pg_line_collidepolygon, METH_FASTCALL, + NULL}, {"as_rect", (PyCFunction)pg_line_as_rect, METH_NOARGS, NULL}, {"update", (PyCFunction)pg_line_update, METH_FASTCALL, NULL}, {"move", (PyCFunction)pg_line_move, METH_FASTCALL, NULL}, diff --git a/src_c/polygon.c b/src_c/polygon.c index 1947c6c5..481eea19 100644 --- a/src_c/polygon.c +++ b/src_c/polygon.c @@ -1190,7 +1190,6 @@ pg_polygon_is_convex(pgPolygonObject *self, PyObject *_null) { return PyBool_FromLong(_pg_polygon_is_convex_helper(&self->polygon)); } - static int _pg_polygon_scale_helper(pgPolygonBase *poly, double factor) { @@ -1258,6 +1257,27 @@ pg_polygon_scale_ip(pgPolygonObject *self, PyObject *arg) Py_RETURN_NONE; } +static PyObject * +pg_polygon_collideline(pgPolygonObject *self, PyObject *const *args, + Py_ssize_t nargs) +{ + pgLineBase line; + int only_edges = 0; + + /* Check for the optional only_edges argument */ + if (PyBool_Check(args[nargs - 1])) { + only_edges = args[nargs - 1] == Py_True; + nargs--; + } + + if (!pgLine_FromObjectFastcall(args, nargs, &line)) { + return RAISE(PyExc_TypeError, "Invalid line parameter"); + } + + return PyBool_FromLong( + pgCollision_PolygonLine(&self->polygon, &line, only_edges)); +} + static PyObject * pg_polygon_collidecircle(pgPolygonObject *self, PyObject *const *args, Py_ssize_t nargs) @@ -1287,6 +1307,7 @@ static struct PyMethodDef pg_polygon_methods[] = { {"rotate_ip", (PyCFunction)pg_polygon_rotate_ip, METH_FASTCALL, NULL}, {"collidepoint", (PyCFunction)pg_polygon_collidepoint, METH_FASTCALL, NULL}, + {"collideline", (PyCFunction)pg_polygon_collideline, METH_FASTCALL, NULL}, {"collidecircle", (PyCFunction)pg_polygon_collidecircle, METH_FASTCALL, NULL}, {"get_bounding_box", (PyCFunction)pg_polygon_get_bounding_box, METH_NOARGS, diff --git a/test/test_line.py b/test/test_line.py index 96d2704f..eaddaa91 100644 --- a/test/test_line.py +++ b/test/test_line.py @@ -3,7 +3,7 @@ from pygame import Vector2, Vector3, Rect -from geometry import Circle, Line +from geometry import Circle, Line, Polygon, regular_polygon E_T = "Expected True, " E_F = "Expected False, " @@ -1098,6 +1098,169 @@ def test_meth_perpendicular_argtype(self): with self.assertRaises(TypeError): l.is_perpendicular(value) + def test_collidepolygon_argtype(self): + """Tests if the function correctly handles incorrect types as parameters""" + + invalid_types = ( + True, + False, + None, + [], + "1", + (1,), + 1, + 0, + -1, + 1.23, + (1, 2, 3), + Circle(10, 10, 4), + Line(10, 10, 4, 4), + Rect(10, 10, 4, 4), + Vector3(10, 10, 4), + Vector2(10, 10), + ) + + l = Line(0, 0, 1, 1) + + for value in invalid_types: + with self.assertRaises(TypeError): + l.collidepolygon(value) + with self.assertRaises(TypeError): + l.collidepolygon(value, True) + with self.assertRaises(TypeError): + l.collidepolygon(value, False) + + def test_collidepolygon_argnum(self): + """Tests if the function correctly handles incorrect number of parameters""" + l = Line(0, 0, 1, 1) + + poly = Polygon((-5, 0), (5, 0), (0, 5)) + invalid_args = [ + (poly, poly), + (poly, poly, poly), + (poly, poly, poly, poly), + ] + + with self.assertRaises(TypeError): + l.collidepolygon() + + for arg in invalid_args: + with self.assertRaises(TypeError): + l.collidepolygon(*arg) + with self.assertRaises(TypeError): + l.collidepolygon(*arg, True) + with self.assertRaises(TypeError): + l.collidepolygon(*arg, False) + + def test_collidepolygon_return_type(self): + """Tests if the function returns the correct type""" + l = Line(0, 0, 1, 1) + + vertices = [(-5, 0), (5, 0), (0, 5)] + + items = [ + Polygon(vertices), + vertices, + tuple(vertices), + [list(v) for v in vertices], + ] + + for item in items: + self.assertIsInstance(l.collidepolygon(item), bool) + self.assertIsInstance(l.collidepolygon(item, True), bool) + self.assertIsInstance(l.collidepolygon(item, False), bool) + + self.assertIsInstance(l.collidepolygon(*vertices), bool) + self.assertIsInstance(l.collidepolygon(*vertices, True), bool) + self.assertIsInstance(l.collidepolygon(*vertices, False), bool) + + def test_collidepolygon_no_invalidation(self): + """Ensures that the function doesn't modify the polygon or the circle""" + l = Line((0, 0), (1, 1)) + poly = Polygon((-5, 0), (5, 0), (0, 5)) + + l_copy = l.copy() + poly_copy = poly.copy() + + l.collidepolygon(poly) + + self.assertEqual(l.a, l_copy.a) + self.assertEqual(l.b, l_copy.b) + + self.assertEqual(poly.vertices, poly_copy.vertices) + self.assertEqual(poly.verts_num, poly_copy.verts_num) + self.assertEqual(poly.c_x, poly_copy.c_x) + self.assertEqual(poly.c_y, poly_copy.c_y) + + def test_collidepolygon_invalid_only_edges_param(self): + """Tests if the function correctly handles incorrect types as only_edges parameter""" + l = Line(0, 0, 1, 1) + poly = Polygon((-5, 0), (5, 0), (0, 5)) + + invalid_types = ( + None, + [], + "1", + (1,), + 1, + 0, + -1, + 1.23, + (1, 2, 3), + Circle(10, 10, 4), + Line(10, 10, 4, 4), + Rect(10, 10, 4, 4), + Vector3(10, 10, 4), + Vector2(10, 10), + ) + + for value in invalid_types: + with self.assertRaises(TypeError): + l.collidepolygon(poly, value) + + def test_collideline(self): + """Ensures that the collidepolygon method correctly determines if a Polygon + is colliding with the Line""" + + l = Line(0, 0, 10, 10) + p1 = regular_polygon(4, l.midpoint, 100) + p2 = Polygon((100, 100), (150, 150), (150, 100)) + p3 = regular_polygon(4, l.a, 10) + p4 = Polygon((5, 5), (5, 10), (0, 10), (2.5, 2.5)) + p5 = Polygon((0, 0), (0, 10), (-5, 10), (-5, 0)) + + # line inside polygon + self.assertTrue(p1.collideline(l)) + + # line outside polygon + self.assertFalse(p2.collideline(l)) + + # line intersects polygon edge + self.assertTrue(p3.collideline(l)) + + # line intersects polygon vertex + self.assertTrue(p4.collideline(l)) + + # line touches polygon vertex + self.assertTrue(p5.collideline(l)) + + # --- Edge only --- + + # line inside polygon + self.assertFalse(p1.collideline(l, True)) + + # line outside polygon + self.assertFalse(p2.collideline(l, True)) + + # line intersects polygon edge + self.assertTrue(p3.collideline(l, True)) + + # line intersects polygon vertex + self.assertTrue(p4.collideline(l, True)) + + # line touches polygon vertex + self.assertTrue(p5.collideline(l, True)) + def test_meth_as_points(self): """Test the as_points method.""" l = Line(0, 0, 1, 1) diff --git a/test/test_polygon.py b/test/test_polygon.py index b09241e6..75661977 100644 --- a/test/test_polygon.py +++ b/test/test_polygon.py @@ -2016,6 +2016,168 @@ def test_scale_argnum(self): with self.assertRaises(TypeError): poly.scale(*arg) + def test_collideline_argtype(self): + """Tests if the function correctly handles incorrect types as parameters""" + + invalid_types = ( + True, + False, + None, + [], + "1", + (1,), + 1, + 0, + -1, + 1.23, + (1, 2, 3), + Circle(10, 10, 4), + # Rect(10, 10, 4, 4), + Vector3(10, 10, 4), + Vector2(10, 10), + Polygon((0, 0), (0, 1), (1, 1), (1, 0)), + ) + + p = Polygon((0, 0), (0, 1), (1, 1), (1, 0)) + + for value in invalid_types: + with self.assertRaises(TypeError): + p.collideline(value) + with self.assertRaises(TypeError): + p.collideline(value, True) + with self.assertRaises(TypeError): + p.collideline(value, False) + + def test_collideline_argnum(self): + """Tests if the function correctly handles incorrect number of parameters""" + l = Line(0, 0, 1, 1) + + p = Polygon((-5, 0), (5, 0), (0, 5)) + invalid_args = [ + (l, l), + (l, l, l), + (l, l, l, l), + ] + + with self.assertRaises(TypeError): + p.collideline() + + for arg in invalid_args: + with self.assertRaises(TypeError): + p.collideline(*arg) + with self.assertRaises(TypeError): + p.collideline(*arg, True) + with self.assertRaises(TypeError): + p.collideline(*arg, False) + + def test_collideline_return_type(self): + """Tests if the function returns the correct type""" + p = Polygon((-5, 0), (5, 0), (0, 5)) + + items = [ + Line(0, 0, 1, 1), + [0, 0, 1, 1], + (0, 0, 1, 1), + [(0, 0), (1, 1)], + ((0, 0), (1, 1)), + ] + + for item in items: + self.assertIsInstance(p.collideline(item), bool) + self.assertIsInstance(p.collideline(item, True), bool) + self.assertIsInstance(p.collideline(item, False), bool) + for item in items[1:]: + self.assertIsInstance(p.collideline(*item), bool) + self.assertIsInstance(p.collideline(*item, True), bool) + self.assertIsInstance(p.collideline(*item, False), bool) + + def test_collideline_no_invalidation(self): + """Ensures that the function doesn't modify the polygon or the circle""" + l = Line((0, 0), (1, 1)) + poly = Polygon((-5, 0), (5, 0), (0, 5)) + + l_copy = l.copy() + poly_copy = poly.copy() + + poly.collideline(l) + + self.assertEqual(l.a, l_copy.a) + self.assertEqual(l.b, l_copy.b) + + self.assertEqual(poly.vertices, poly_copy.vertices) + self.assertEqual(poly.verts_num, poly_copy.verts_num) + self.assertEqual(poly.c_x, poly_copy.c_x) + self.assertEqual(poly.c_y, poly_copy.c_y) + + def test_collideline_invalid_only_edges_param(self): + """Tests if the function correctly handles incorrect types as only_edges parameter""" + l = Line(0, 0, 1, 1) + poly = Polygon((-5, 0), (5, 0), (0, 5)) + + invalid_types = ( + None, + [], + "1", + (1,), + 1, + 0, + -1, + 1.23, + (1, 2, 3), + Circle(10, 10, 4), + Line(10, 10, 4, 4), + Rect(10, 10, 4, 4), + Vector3(10, 10, 4), + Vector2(10, 10), + ) + + for value in invalid_types: + with self.assertRaises(TypeError): + poly.collideline(l, value) + + def test_collidepolygon(self): + """Ensures that the collidepolygon method correctly determines if a Polygon + is colliding with the Line""" + + l = Line(0, 0, 10, 10) + p1 = regular_polygon(4, l.midpoint, 100) + p2 = Polygon((100, 100), (150, 150), (150, 100)) + p3 = regular_polygon(4, l.a, 10) + p4 = Polygon((5, 5), (5, 10), (0, 10), (2.5, 2.5)) + p5 = Polygon((0, 0), (0, 10), (-5, 10), (-5, 0)) + + # line inside polygon + self.assertTrue(l.collidepolygon(p1)) + + # line outside polygon + self.assertFalse(l.collidepolygon(p2)) + + # line intersects polygon edge + self.assertTrue(l.collidepolygon(p3)) + + # line intersects polygon vertex + self.assertTrue(l.collidepolygon(p4)) + + # line touches polygon vertex + self.assertTrue(l.collidepolygon(p5)) + + # --- Edge only --- + + # line inside polygon + self.assertFalse(l.collidepolygon(p1, True)) + + # line outside polygon + self.assertFalse(l.collidepolygon(p2, True)) + + # line intersects polygon edge + self.assertTrue(l.collidepolygon(p3, True)) + + # line intersects polygon vertex + self.assertTrue(l.collidepolygon(p4, True)) + + # line touches polygon vertex + self.assertTrue(l.collidepolygon(p5, True)) + if __name__ == "__main__": unittest.main()