From 74a4209c668165c6773a344cbbc2fba5865481d9 Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Sat, 11 Feb 2023 11:26:38 +0100 Subject: [PATCH 01/12] base --- geometry.pyi | 2 ++ src_c/collisions.c | 64 ++++++++++++++++++++++++++++++++++++++ src_c/include/collisions.h | 3 ++ src_c/line.c | 28 +++++++++++++++++ src_c/polygon.c | 23 ++++++++++++++ 5 files changed, 120 insertions(+) diff --git a/geometry.pyi b/geometry.pyi index fd3f053f..f56344bc 100644 --- a/geometry.pyi +++ b/geometry.pyi @@ -109,6 +109,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) -> bool: ... def as_rect(self) -> Rect: ... @overload def move(self, x: float, y: float) -> Line: ... @@ -214,6 +215,7 @@ class Polygon: def collidepoint(self, x: float, y: float) -> bool: ... @overload def collidepoint(self, point: Coordinate) -> bool: ... + def collideline(self, line: LineValue) -> bool: ... def get_bounding_box(self) -> Rect: ... def is_convex() -> bool: ... def insert_vertex(self, index: int, vertex: Coordinate) -> None: ... diff --git a/src_c/collisions.c b/src_c/collisions.c index 771627bd..cda48a26 100644 --- a/src_c/collisions.c +++ b/src_c/collisions.c @@ -319,6 +319,70 @@ 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_edges(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 = 0; + collision = _pgCollision_line_edges(line, poly); + + if (collision || only_edges) { + return collision; + } + + return pgCollision_PolygonPoints(poly, line->x1, line->y1, line->x2, + line->y2); +} + static int pgRaycast_LineLine(pgLineBase *A, pgLineBase *B, double max_t, double *T) { diff --git a/src_c/include/collisions.h b/src_c/include/collisions.h index ff077279..46324e47 100644 --- a/src_c/include/collisions.h +++ b/src_c/include/collisions.h @@ -43,4 +43,7 @@ pgRaycast_LineCircle(pgLineBase *, pgCircleBase *, double, double *); static int pgCollision_PolygonPoint(pgPolygonBase *, double, double); +static int +pgCollision_PolygonLine(pgPolygonBase *, pgLineBase *, int); + #endif /* ~_PG_COLLISIONS_H */ diff --git a/src_c/line.c b/src_c/line.c index 2b4328b1..77a0493c 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -490,6 +490,33 @@ pg_line_flip_ip(pgLineObject *self, PyObject *_null) Py_RETURN_NONE; } +static PyObject * +pg_line_collidepolygon(pgLineObject *self, PyObject *const *args, + Py_ssize_t nargs) +{ + pgPolygonBase poly; + int was_sequence, result = 0, only_edges = 0; + + if (PyBool_Check(args[nargs - 1])) { + if (args[nargs - 1] == Py_True) { + only_edges = 1; + } + 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 struct PyMethodDef pg_line_methods[] = { {"__copy__", (PyCFunction)pg_line_copy, METH_NOARGS, NULL}, {"copy", (PyCFunction)pg_line_copy, METH_NOARGS, NULL}, @@ -501,6 +528,7 @@ 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 43d38a35..ec2111a6 100644 --- a/src_c/polygon.c +++ b/src_c/polygon.c @@ -1134,6 +1134,28 @@ pg_polygon_is_convex(pgPolygonObject *self, PyObject *_null) return PyBool_FromLong(_pg_polygon_is_convex_helper(&self->polygon)); } +static PyObject * +pg_polygon_collideline(pgPolygonObject *self, PyObject *const *args, + Py_ssize_t nargs) +{ + pgLineBase line; + int only_edges = 0; + + if (PyBool_Check(args[nargs - 1])) { + if (args[nargs - 1] == Py_True) { + only_edges = 1; + } + 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 struct PyMethodDef pg_polygon_methods[] = { {"move", (PyCFunction)pg_polygon_move, METH_FASTCALL, NULL}, {"move_ip", (PyCFunction)pg_polygon_move_ip, METH_FASTCALL, NULL}, @@ -1141,6 +1163,7 @@ static struct PyMethodDef pg_polygon_methods[] = { {"rotate_ip", (PyCFunction)pg_polygon_rotate_ip, METH_O, NULL}, {"collidepoint", (PyCFunction)pg_polygon_collidepoint, METH_FASTCALL, NULL}, + {"collideline", (PyCFunction)pg_polygon_collideline, METH_FASTCALL, NULL}, {"get_bounding_box", (PyCFunction)pg_polygon_get_bounding_box, METH_NOARGS, NULL}, {"is_convex", (PyCFunction)pg_polygon_is_convex, METH_NOARGS, NULL}, From 56069fd3af9d9608706d2c1b8c8a5ae4f3f3f4d0 Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Mon, 13 Feb 2023 12:53:05 +0100 Subject: [PATCH 02/12] added docs --- docs/geometry.rst | 4 ++++ docs/line.rst | 23 +++++++++++++++++++++++ docs/polygon.rst | 23 +++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/docs/geometry.rst b/docs/geometry.rst index 80fef0b0..5b2b623d 100644 --- a/docs/geometry.rst +++ b/docs/geometry.rst @@ -120,6 +120,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. Additionally to these, the line shape can also be used as a collider for the ``geometry.raycast`` function. @@ -154,6 +156,8 @@ other objects. collidepoint: Checks if the polygon collides with the given point. + collideline: Checks if the polygon collides with the given line. + insert_vertex: Adds a vertex to the polygon. remove_vertex: Removes a vertex from the polygon. diff --git a/docs/line.rst b/docs/line.rst index 01037aa6..6f5c7ba0 100644 --- a/docs/line.rst +++ b/docs/line.rst @@ -296,6 +296,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` intersects the `Line`. + It takes either a `Polygon` or Polygon-like object as an argument and it returns + `True` if the polygon overlaps 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 5733cd80..5219952c 100644 --- a/docs/polygon.rst +++ b/docs/polygon.rst @@ -157,6 +157,29 @@ 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` intersects the `Polygon`. + It takes either a `Line` or Line-like object as an argument and it returns `True` + if the `Line` overlaps 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. + 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. + + .. ## Polygon.collideline ## + .. method:: copy | :sl:`returns a copy of the polygon` From 8ee01231a1270cfe3b1e00e460f602457a068a9b Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Mon, 13 Feb 2023 12:54:50 +0100 Subject: [PATCH 03/12] removed redundant thing in docs --- docs/line.rst | 4 ++-- docs/polygon.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/line.rst b/docs/line.rst index 6f5c7ba0..9d276f6b 100644 --- a/docs/line.rst +++ b/docs/line.rst @@ -303,9 +303,9 @@ Line Methods | :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` intersects the `Line`. + 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 overlaps with the `Line`, `False` otherwise. + `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 diff --git a/docs/polygon.rst b/docs/polygon.rst index 5219952c..15e47a27 100644 --- a/docs/polygon.rst +++ b/docs/polygon.rst @@ -164,9 +164,9 @@ Polygon Methods | :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` intersects the `Polygon`. + 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` overlaps with the `Polygon`, `False` otherwise. + 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 From e58fa7501fea50ef3ee90d221ba3dcfbf7d73ade Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Mon, 13 Feb 2023 12:56:32 +0100 Subject: [PATCH 04/12] cleanup --- src_c/collisions.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src_c/collisions.c b/src_c/collisions.c index cda48a26..e7453084 100644 --- a/src_c/collisions.c +++ b/src_c/collisions.c @@ -352,16 +352,15 @@ pgCollision_PolygonPoints(pgPolygonBase *poly, double x1, double y1, double x2, } static inline int -_pgCollision_line_edges(pgLineBase *line, pgPolygonBase *poly) +_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]})) { + if (pgCollision_LineLine( + line, &(pgLineBase){vertices[j * 2], vertices[j * 2 + 1], + vertices[i * 2], vertices[i * 2 + 1]})) { return 1; } } @@ -372,8 +371,7 @@ _pgCollision_line_edges(pgLineBase *line, pgPolygonBase *poly) static inline int pgCollision_PolygonLine(pgPolygonBase *poly, pgLineBase *line, int only_edges) { - int collision = 0; - collision = _pgCollision_line_edges(line, poly); + int collision = _pgCollision_line_polyedges(line, poly); if (collision || only_edges) { return collision; From 28168ff2323fa5dadf92c4ebbb3436a8a5d9016c Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Mon, 13 Feb 2023 12:59:11 +0100 Subject: [PATCH 05/12] comment and minor change --- src_c/line.c | 3 ++- src_c/polygon.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src_c/line.c b/src_c/line.c index 77a0493c..313e600f 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -495,8 +495,9 @@ pg_line_collidepolygon(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) { pgPolygonBase poly; - int was_sequence, result = 0, only_edges = 0; + int was_sequence, result, only_edges = 0; + /* Check for the optional only_edges argument */ if (PyBool_Check(args[nargs - 1])) { if (args[nargs - 1] == Py_True) { only_edges = 1; diff --git a/src_c/polygon.c b/src_c/polygon.c index ec2111a6..aa0d49d6 100644 --- a/src_c/polygon.c +++ b/src_c/polygon.c @@ -1141,6 +1141,7 @@ pg_polygon_collideline(pgPolygonObject *self, PyObject *const *args, pgLineBase line; int only_edges = 0; + /* Check for the optional only_edges argument */ if (PyBool_Check(args[nargs - 1])) { if (args[nargs - 1] == Py_True) { only_edges = 1; From 63ed62d9ac9527201d95cd49851a755429b8a4be Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Mon, 13 Feb 2023 14:07:57 +0100 Subject: [PATCH 06/12] corrected functions signatures and slight changes --- geometry.pyi | 4 ++-- src_c/line.c | 4 +--- src_c/polygon.c | 4 +--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/geometry.pyi b/geometry.pyi index f56344bc..c46892a4 100644 --- a/geometry.pyi +++ b/geometry.pyi @@ -109,7 +109,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) -> bool: ... + def collidepolygon(self, polygon: Polygon, only_edges: bool = False) -> bool: ... def as_rect(self) -> Rect: ... @overload def move(self, x: float, y: float) -> Line: ... @@ -215,7 +215,7 @@ class Polygon: def collidepoint(self, x: float, y: float) -> bool: ... @overload def collidepoint(self, point: Coordinate) -> bool: ... - def collideline(self, line: LineValue) -> bool: ... + def collideline(self, line: LineValue, only_edges: bool = False) -> bool: ... def get_bounding_box(self) -> Rect: ... def is_convex() -> bool: ... def insert_vertex(self, index: int, vertex: Coordinate) -> None: ... diff --git a/src_c/line.c b/src_c/line.c index 313e600f..a7d2fac8 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -499,9 +499,7 @@ pg_line_collidepolygon(pgLineObject *self, PyObject *const *args, /* Check for the optional only_edges argument */ if (PyBool_Check(args[nargs - 1])) { - if (args[nargs - 1] == Py_True) { - only_edges = 1; - } + only_edges = args[nargs - 1] == Py_True; nargs--; } diff --git a/src_c/polygon.c b/src_c/polygon.c index aa0d49d6..7f12889b 100644 --- a/src_c/polygon.c +++ b/src_c/polygon.c @@ -1143,9 +1143,7 @@ pg_polygon_collideline(pgPolygonObject *self, PyObject *const *args, /* Check for the optional only_edges argument */ if (PyBool_Check(args[nargs - 1])) { - if (args[nargs - 1] == Py_True) { - only_edges = 1; - } + only_edges = args[nargs - 1] == Py_True; nargs--; } From 15811104edbd4e6f381c21e3da59d139f46bf623 Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Mon, 13 Feb 2023 14:22:00 +0100 Subject: [PATCH 07/12] Added python example --- examples/polygon_line_collision.py | 105 +++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 examples/polygon_line_collision.py diff --git a/examples/polygon_line_collision.py b/examples/polygon_line_collision.py new file mode 100644 index 00000000..e9cca14e --- /dev/null +++ b/examples/polygon_line_collision.py @@ -0,0 +1,105 @@ +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() +def rotate_line(line, angle): + angle = radians(angle) + + x = line.x2 - line.x1 + y = line.y2 - line.y1 + + x1 = x * cos(angle) - y * sin(angle) + y1 = x * sin(angle) + y * cos(angle) + + line.b = (x1 + line.x1, y1 + line.y1) + + +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() From 0f64cb7ae351975a05ee00032e168e99ee79fa96 Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Mon, 13 Feb 2023 14:35:11 +0100 Subject: [PATCH 08/12] more correct rotate func --- examples/polygon_line_collision.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/examples/polygon_line_collision.py b/examples/polygon_line_collision.py index e9cca14e..dbc733e9 100644 --- a/examples/polygon_line_collision.py +++ b/examples/polygon_line_collision.py @@ -5,17 +5,20 @@ from geometry import Line, regular_polygon -# using this because we're missing line.rotate() -def rotate_line(line, angle): - angle = radians(angle) - - x = line.x2 - line.x1 - y = line.y2 - line.y1 - - x1 = x * cos(angle) - y * sin(angle) - y1 = x * sin(angle) + y * cos(angle) - - line.b = (x1 + line.x1, y1 + line.y1) +# 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(d_ang), sin(d_ang) + 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() From 771ae65b4a958c6a511e602f8a107109f6ed057e Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Mon, 13 Feb 2023 14:35:42 +0100 Subject: [PATCH 09/12] ups --- examples/polygon_line_collision.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/polygon_line_collision.py b/examples/polygon_line_collision.py index dbc733e9..5b3bcc63 100644 --- a/examples/polygon_line_collision.py +++ b/examples/polygon_line_collision.py @@ -8,7 +8,7 @@ # 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(d_ang), sin(d_ang) + ca, sa = cos(angle), sin(angle) xm, ym = line.midpoint ax, ay = line.a bx, by = line.b From 69abfe17762e5e596390bd4dad388ab3dd881251 Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Mon, 13 Feb 2023 16:12:54 +0100 Subject: [PATCH 10/12] Unittests round 1. Fixed segfault with polygons passed as lines. --- src_c/line.c | 8 +++- test/test_line.py | 96 +++++++++++++++++++++++++++++++++++++++++++- test/test_polygon.py | 95 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 195 insertions(+), 4 deletions(-) diff --git a/src_c/line.c b/src_c/line.c index a7d2fac8..2f8ab14b 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; @@ -527,7 +530,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}, + {"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/test/test_line.py b/test/test_line.py index 8a757e61..1c9545c6 100644 --- a/test/test_line.py +++ b/test/test_line.py @@ -2,7 +2,7 @@ from pygame import Vector2, Vector3, Rect -from geometry import Circle, Line +from geometry import Circle, Line, Polygon E_T = "Expected True, " E_F = "Expected False, " @@ -1004,6 +1004,100 @@ 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) + if __name__ == "__main__": unittest.main() diff --git a/test/test_polygon.py b/test/test_polygon.py index 0a50d873..d41ff914 100644 --- a/test/test_polygon.py +++ b/test/test_polygon.py @@ -4,7 +4,7 @@ from pygame import Vector2, Vector3, Rect import geometry -from geometry import Polygon +from geometry import Polygon, Line, Circle import math @@ -1621,6 +1621,99 @@ def test_is_convex_meth(self): self.assertTrue(p1.is_convex()) self.assertFalse(p2.is_convex()) + 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) + if __name__ == "__main__": unittest.main() From a4d80a20bd775bf72932124f5e8600b8cd1593ac Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Mon, 13 Feb 2023 17:11:46 +0100 Subject: [PATCH 11/12] unittests round 2 --- test/test_line.py | 71 +++++++++++++++++++++++++++++++++++++++++++- test/test_polygon.py | 71 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 2 deletions(-) diff --git a/test/test_line.py b/test/test_line.py index 1c9545c6..89165551 100644 --- a/test/test_line.py +++ b/test/test_line.py @@ -2,7 +2,7 @@ from pygame import Vector2, Vector3, Rect -from geometry import Circle, Line, Polygon +from geometry import Circle, Line, Polygon, regular_polygon E_T = "Expected True, " E_F = "Expected False, " @@ -1098,6 +1098,75 @@ def test_collidepolygon_no_invalidation(self): 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)) + if __name__ == "__main__": unittest.main() diff --git a/test/test_polygon.py b/test/test_polygon.py index d41ff914..0d128476 100644 --- a/test/test_polygon.py +++ b/test/test_polygon.py @@ -4,7 +4,7 @@ from pygame import Vector2, Vector3, Rect import geometry -from geometry import Polygon, Line, Circle +from geometry import Polygon, Line, Circle, regular_polygon import math @@ -1714,6 +1714,75 @@ def test_collideline_no_invalidation(self): 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() From 0bcfd107c54941c902b6906a0ff3d97a31cd2cbf Mon Sep 17 00:00:00 2001 From: itzpr3d4t0r <103119829+itzpr3d4t0r@users.noreply.github.com> Date: Thu, 6 Jul 2023 19:23:06 +0200 Subject: [PATCH 12/12] format and fix --- test/test_polygon.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_polygon.py b/test/test_polygon.py index 1fde8cfd..75661977 100644 --- a/test/test_polygon.py +++ b/test/test_polygon.py @@ -1708,7 +1708,7 @@ def test_is_convex_meth(self): self.assertTrue(p1.is_convex()) self.assertFalse(p2.is_convex()) - def test_collidecircle_argtype(self): + def test_collidecircle_argtype(self): """Tests if the function correctly handles incorrect types as parameters""" invalid_types = ( @@ -2015,6 +2015,7 @@ def test_scale_argnum(self): for arg in invalid_args: with self.assertRaises(TypeError): poly.scale(*arg) + def test_collideline_argtype(self): """Tests if the function correctly handles incorrect types as parameters"""