pygame-community / pygame-geometry Goto Github PK
View Code? Open in Web Editor NEW๐๐ฎ-๐ท pygame with polygons, circles, lines, and raycasting
License: GNU General Public License v3.0
๐๐ฎ-๐ท pygame with polygons, circles, lines, and raycasting
License: GNU General Public License v3.0
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();
Example:
a = Line(1, 1, 0, 0)
b = Line(2, 1, 1, 0)
a.is_parallel(b)
We are still missing a line.midpoint attribute ( x and y singularly included).
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.
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.
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
.
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.
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)
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.
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
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.
This method would return the smallest rectangle that contains the polygon as a pygame.Rect object.
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
We still have many functions that don't follow the PEP8 python naming convention. https://peps.python.org/pep-0008/#function-and-variable-names for reference. We should go through our functions and rename them when needed
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.
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
This method would return a boolean value indicating whether the polygon is convex or not.
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.
Switches points A and B around
import geometry
Looks too boring...
import pygame_geometry
Looks more fun!
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.
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()
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))
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);
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:
circle.intersect_circle(circle2) -> list[Point]
)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:
shape.intersect_SHAPE_NAMEs( )
circle.intersect_circles()
, line.intersect_lines()
andpolygon.intersect_polygons()
)shape.intersect_shapes( )
Line.intersect_shapes(shapes: Sequence[Union[Circle, Line, Polygon]]) -> List[Tuple[Tuple[float, float]]]
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.
Would be cool to have, it would center in the line's midpoint and take r=line_length/2.
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]
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
@avaxar has ran into a problem while running the unit tests on his machine as shown here:
needs_to_be_rebuild
-> needs_to_be_rebuilt
Assigned to @maqa41
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:
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)pgCollision_LinePoint
function into a precise one and one with this "collision width" for visual correctness scenariosepsilon
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.
@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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.