Giter Site home page Giter Site logo

pygame-community / pygame-geometry Goto Github PK

View Code? Open in Web Editor NEW
25.0 5.0 11.0 798 KB

๐Ÿ๐ŸŽฎ-๐Ÿ”ท pygame with polygons, circles, lines, and raycasting

License: GNU General Public License v3.0

Python 58.29% C 41.71%
pygame polygon gamedev collision collisions math

pygame-geometry's Introduction

pygame_geometry

PRS Welcome Python Code Quality C++ Code Quality Ubuntu latest Windows latest MacOS latest Commits per week Code style: black

pygame_geometry is a free Python implementation of polygons, circles, lines, and raycasting. The main purpose of this repository is to help users integrate special colliders easier for their video game.

Everything you see in this repository is subject to change as this project is heavily in development. The project is set to be migrated to the official pygame-ce repository.

Installation (Python 3.7+)

Please follow this guide to install pygame_geometry.

Help

If you're getting started with this library, you can check the docs directory to see the documentation or the examples directory to see the use-case of this library.

Credits

Thanks to Emc2356 and itzpr3d4t0r for the majority of the pygame.geometry work.

This project is under the supervision of Starbuck5 & novialriptide.

pygame-geometry's People

Contributors

avaxar avatar blankriot96 avatar dependabot[bot] avatar emc2356 avatar gresm avatar itzpr3d4t0r avatar maqa41 avatar matiiss avatar newpaxonian avatar novialriptide avatar scriptlinestudios avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

pygame-geometry's Issues

Multiple raycasts at once

This must not be worked on until #127 is fixed and #126 is merged.

The goal of this function is so that the user can perform multiple raycasts at once in a single function. This will potentially boost performance as seeing @ScriptLineStudios's video game for the Halloween Game Jam has gotten a significant performance boost when using Line.raycast().

Possible name contenders for this function are raycastlist() or multiraycast()

Thank you @ScriptLineStudios for using our experimental library! Below is a video of a possible use case for a multiple raycast function.

8mb.video-iUD-h4PNnBSi.mp4

Polygon.update_vertex()

I think a function to update one of the vertices of a Polygon would be useful. I propose something like this.

polygon = geometry.Polygon([(100, 10), (600, 600), (10, 600)])
polygon.update_vertex(0, (200, 10))

pgCircleBase.r_sqr removal

The internal pgCircleBase struct member r_sqr didn't end up being used for any performance intensive calculations and is just saving a multiplication, so I propose we remove it and swap it with r*r.
The r_sqr python variable will remain but will not reference the internal r_sqr anymore but rather r*r.

Separate collision functions from the C types

We plan to have many collision functions for our shapes, and we will probably end up having a case like this:

circle = Circle(10, 4, 20)
line = Line((5, 5), (10, 10))

circle.collideline(line)
line.collidecircle(circle)

This means we have duplicated code for the same collision function.
I propose we take the collision functions themselves(the ones that calculate the true/false value) and put them in standalone internal C functions in another file like collisions.c that we include in line.c and cirlce.c.

In that way we could have a single collision implementation that will clean up code a lot. We could have something like:

#include "line.h"
#include "circle.h"

int collision_line_circle(pgCircleBase *c, pgLineBase *l);
int collision_line_rect(pgLineBase *c, SDL_Rect *r);
int collision_circle_rect(pgCircleBase *c, SDL_Rect *r);

Assigning to polygon subscript fails to update center

Currently, if you try to perform an action like polygon[2] = (10, 10), the center of the polygon will not be updated. This means that the feature is not functioning as intended and is broken.

You can use this program to see it:

from geometry import regular_polygon
import pygame


def main():
    pygame.init()
    screen = pygame.display.set_mode((500, 500))
    clock = pygame.time.Clock()
    poly = regular_polygon(3, (250, 250), 75)
    keep_going = True
    while keep_going:
        clock.tick(60)

        screen.fill((0, 0, 0))

        pygame.draw.polygon(screen, (255, 255, 255), poly.vertices, 2)
        pygame.draw.circle(screen, (255, 0, 0), poly.center, 5)

        poly[0] = pygame.mouse.get_pos()

        pygame.display.flip()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                keep_going = False


if __name__ == "__main__":
    main()
    pygame.quit()

Memory leak with string conversion functions

Every function that converts shapes into strings is leaking memory, with print statements unexplicably leaking 10X slower than dunder methods.

Watch out! this is leaking fast i'm not responsible for any memory corruption/OS crash!

I used this code:

from geometry import Line, Circle, Polygon

line = Line((0, 0), (1, 1))
circle = Circle((0, 0), 1)
polygon = Polygon([(0, 0), (1, 1), (1, 0)])
while True:
    line.__repr__()
    # circle.__repr__()
    # polygon.__repr__()
    # line.__str__()
    # circle.__str__()
    # polygon.__str__()
    # print(line)
    # print(circle)
    # print(polygon)

`raycast` & `multiraycast`

i think the API should look something like this:

@overload
def raycast(
    originpos: Coordinate,
    endpos: Coordinate,
    colliders: Sequence[Rect, Circle, Line],
) -> Optional[Tuple[float, float]]: ...
@overload
def raycast(
    originpos: Coordinate,
    angle: float,
    max_dist: float,
    colliders: Sequence[Rect, Circle, Line],
) -> Optional[Tuple[float, float]]: ...


@overload
def multiraycast(
    origin_positions: Sequence[Coordinate],
    end_positions: Sequence[Coordinate],
    colliders: Sequence[Rect, Circle, Line],
) -> Sequence[Optional[Tuple[float, float]]]: ...
@overload
def multiraycast(
    origin_positions: Sequence[Coordinate],
    angle: float,
    max_dist: float,
    colliders: Sequence[Rect, Circle, Line],
) -> Sequence[Optional[Tuple[float, float]]]: ...

it is easy for the user in my opinion

Track intersection functions

Motivation

Right now the only way of doing an "intersection" is the line.raycast() function. The sad truth is that you can't swap a raycast function for an intersection one as:

the raycast() function returns you one or None points every time, specifically it returns the closest intersection point if there are any.

It should be apparent how even a simple intersection between a line and a circle can result in more than one point, so the raycast function would miss out on the second intersection.

We need actual intersection functions for shapes, that can either be:

  • Shape specific ( meaning e.g. circle.intersect_circle(circle2) -> list[Point] )
  • General ( meaning shape.intersect(AnyShape) -> list[Point] )

And as a consequence to this we'll also need a function for intersecting a shape with a list of shapes, this would be more complex and should not require to make a version for each shape combination, although it would be the right thing to do to have at least a e.g. circle.intersect_circles(), just like rect has a collidelist that collides with a list of rects only.

So we would also need:

  • A Shape specific shape.intersect_SHAPE_NAMEs( )
    These would be 3 functions, 1 for each shape meaning circle.intersect_circles(), line.intersect_lines() and
    polygon.intersect_polygons() )
  • A General shape.intersect_shapes( )
    The function would behave somewhat like this:
Line.intersect_shapes(shapes: Sequence[Union[Circle, Line, Polygon]]) -> List[Tuple[Tuple[float, float]]]

Implementation

My idea is to have these function return a list of points in the form tuple[float, float] so we'd have: list[tuple[float, float]] and when we don't find an intersection point we just return an empty list.

Line.as_circle()

Would be cool to have, it would center in the line's midpoint and take r=line_length/2.

Move `Line.raycast()` to `raycast()`

Something like this? This is a low priority for now, but I highly suggest this issue must be worked on before we migrate to https://github.com/pygame/pygame since Ray != Line
As @Starbuck5 suggested, we can do a single function that's not attached to any instance such as

raycast(
    start_pos: Sequence[int, int],
    endpoint: Union[Sequence[float, float], None] = None,
    angle: Union[float, None] = None
)
# If the angle and endpoint both have values or are None, it'll raise an error.

The re-implementation should be pretty simple since the C implementation already exists.

Inconsistent Python versions used in GitHub Actions builds

I recently noticed that our GitHub Actions builds use different Python versions for different operating systems. Specifically, the builds for macOS use Python 3.11, the builds for Ubuntu use Python 3.10, and the builds for Windows use Python 3.9 or something similar.

Instead of viewing this as a problem, I believe it is an opportunity for us to expand our build configurations and ensure compatibility with a wider range of Python versions. This will allow us to support a larger number of users and potentially attract a wider audience.

Pong, but it uses our library

One of the most popular starting pygame projects is Pong, but many people use pygame.Rect for its collisions. To demonstrate our library in the most simplest way possible, I propose creating a Pong example after we have finalized our API design.

I would also like to create new coding conventions for future examples so they can all stay consistent.

Deprecation warning appears after installing Python 3.11

I'm getting this deprecation warning when installing this library with Python 3.11.

PS C:\Users\novia\documents\github\pygame_geometry> pip install .
Processing c:\users\novia\documents\github\pygame_geometry
  Preparing metadata (setup.py) ... done
Installing collected packages: geometry
  DEPRECATION: geometry is being installed using the legacy 'setup.py install' method, because it does not have a 'pyproject.toml' and the 'wheel' package is not installed. pip 23.1 will enforce this behaviour change. A possible replacement is to enable the '--use-pep517' option. Discussion can be found at https://github.com/pypa/pip/issues/8559
  Running setup.py install for geometry ... done
Successfully installed geometry-0.0.0

`RegularPolygon` implementation

@Emc2356 has already done a PR that does exactly this, but not in what I hoped. #73

To recap, the function is supposed to create a polygon with sides of equal length.

I was thinking of either reimplementing his PR's design to look more like this:

def RegularPolygon(pos, radius) -> Polygon

# So we would use it like this
geometry.RegularPolygon(...)

Or we can do something like this

class RegularPolygon(Polygon):
   def __init__(self, pos, radius): ...

If RegularPolygon becomes a child class of Polygon, the user would be able to modify its radius, origin_pos, etc on the go, where as if we implement the function method, the user would need to create a new Polygon when they want to change its radius as well as using a for loop to change every point to the Polygon

Disallow creating a degenerate line segment

Right now we allow having a "point" line segment:

line = Line(1, 1, 1, 1)

This has to be fixed. many times we divide by x2-x1 or y2-y1 and that will error out.
My idea is to implement a new check function to add inside the pgLine_FromObject function after the line attributes are set that checks their validity. if not it will error out.

Line.raycast() / raycast() internal rewrite

Right now, we are still internally naming a ray as a line in the C code, which I do not think is appropriate.

I propose renaming and reorganizing some C functions to take in double types instead of a pgLineObject for the following functions:

pgIntersection_LineCircle();
pgIntersection_LineLine();
pgIntersection_LineRect();

Add documentation for everything

TO DO

  • pygame_geometry.Circle
  • pygame_geometry.Polygon
  • pygame_geometry.Line

I'm not sure what we can do format-wise, leave comments below what we can do.

geometry.colliderays() or line.colliderays()

Right now we have a line.raycast(LineType) function that calculates collision between two lines.
In many cases when raycasting you'll need to check collision between two line sequences. I propose we add a function that does just that, and in particular is in the form:

colliderays(seq1: Sequence[LineType], seq2: Sequence[LineType]) -> Sequence[Union[Tuple[float, float], None]]

The function will check each line in the first sequence and search for collision with each line in the second sequence. If it didn't find any appends None to the return list, else appends the collision point.
We could even make it so you can pass in an optional parameter that lets you choose to make the function return the collision positions or simply [] or None .
We could have it for 2 line sequences but also between one line and a sequence of lines in which case you'd probably want to implement in in line.c:

- geometry.colliderays(seq1: Sequence[LineType], seq2: Sequence[LineType]) -> Sequence[Union[Tuple[float, float], None]]
- line.colliderays(sequence: Sequence[LineType]) -> Union[Tuple[float, float], None]

improve `setup.py`

i would like to make setup.py being able to detect when a change has happened in any of the source files instead of just checking geometry.c
i also would like to be able to use the setup file like this python setup.py --test --format --install

Add line.collideswith() function

We are still missing a general function for collisions. This one for line should mirror the circle one
here.
Lets also make an effort towards using PEP8 naming convention for functions.

Add as_segments() function to Polygon

This method would return a list of Lines that represents the polygon as a series of connected line segments.

Here is an example of how it could be used in Python:

from geometry import Polygon, Line

polygon = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])

# Get a list of the sides of the polygon
segments = polygon.as_segments()

# segments is a list of Line objects with the following positions:
# [Line((0, 0), (1, 0)), Line((1, 0), (1, 1)), Line((1, 1), (0, 1)), Line((0, 1), (0, 0))]

# Print the length of each side
for segment in segments:
    print(segment.length)

The output of this program would be:

1.0
1.0
1.0
1.0

Pygame RectStyle to Polygon function

It would be pretty useful especially for having a quick way to convert a rect to a polygon for then operating transformations such as rotations that aren't possible on a pygame rect.

I think the function should be METH_FASTCALL in tune with the pygame implementation, where functions that accept rectangles also accept RectSyle inputs.

I believe it is possible to implement this same function in two ways, namely as part of the Polygon class or as a standalone function in the general geometry module. In the first case it would be a matter of having a function of the type polygon.from_rect() or something similar, while in the second case something like geometry.polygon_fromrect().

Personally, I preferred the latter since having a function that can be called from any variable of type Polygon could misunderstand its use, which is much more generic and not limited to being a member function of the Polygon class, and also there could be cases such as tomato.from_rect() where the tomato variable is a Polygon object, but that is not immediately readable and could confuse the inattentive user. Not to mention homogeneous sets such as lists/sets and tuples.

Ideally it would be best to implement this as a Rect function so that one could simply do my_rect.as_polygon(), but that's not possible atm.

Give pgCollision_LinePoint a "collision width"

See https://www.jeffreythompson.org/collision-detection/line-point.php.
Right now the pgCollision_LinePoint is very precise but in a visual application this precision can look strange, as a point and a line that are visually colliding are in reality not colliding.

I therefore propose we do either of the following:

  • Change the pgCollision_LinePoint function to have a "collision width", or a range in which the point counts as colliding even if it's not technically "on the line" (i remember that a line has no thickness)
  • Split the pgCollision_LinePoint function into a precise one and one with this "collision width" for visual correctness scenarios
  • Add an optional epsilon param to the collision functions with lines that lets the user manipulate this "collision width"

Either way we need to act now as this is breaking visual collision tests.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.