Skip to content

Commit

Permalink
chore(physics): ✨ Made the Shapes act more like a regular Shape
Browse files Browse the repository at this point in the history
Also added a warning to the Arc bcos it's very broken and added a ShpGroup for Splittable shapes
  • Loading branch information
Tsunami014 (Max) authored and Tsunami014 (Max) committed Jan 1, 2025
1 parent 898720c commit cf74464
Showing 1 changed file with 64 additions and 25 deletions.
89 changes: 64 additions & 25 deletions BlazeSudio/collisions/lib/collisions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import math
from enum import IntEnum
from typing import Union, Iterable, Any, Dict
from typing import Union, Iterable, Any

# Just for utility funcs, not the actual collision library
try: # This library was not made for pygame, but it can be used with it
Expand Down Expand Up @@ -130,7 +130,9 @@ class ShpGroups(IntEnum):
"""Shapes that make the outer edges of other shapes"""
NOTSTRAIGHT = 2
"""Shapes that are not straight; e.g. Circles"""
GROUP = 3
SPLITTABLE = 3
"""Shapes that can be split into lines (e.g. Polygons)"""
GROUP = 4
"""A group of other shapes"""

class ShpTyps(IntEnum):
Expand Down Expand Up @@ -505,14 +507,16 @@ class Shapes:
"""A class which holds multiple shapes and can be used to do things with all of them at once."""
GROUPS = {ShpGroups.GROUP}
TYPE = ShpTyps.Group
def __init__(self, *shapes: Shape):
def __init__(self, *shapes: Shape, bounciness: float = BASEBOUNCINESS):
"""
Args:
*shapes (Shape): The shapes to start off with in this object.
bounciness (float, optional): How bouncy this object is. 1 = rebounds perfectly, <1 = eventually will stop, >1 = will bounce more each time. Defaults to 0.7.
Example:
`Shapes(Shape1, Shape2)` OR `Shapes(*[Shape1, Shape2])`
"""
self.bounciness = bounciness
self.shapes = list(shapes)

def add_shape(self, shape: Shape) -> None:
Expand Down Expand Up @@ -590,23 +594,34 @@ def whereCollides(self, shapes: Union[Shape,'Shapes',Iterable[Shape]]) -> Iterab

def closestPointTo(self, othershape: Shape, returnAll: bool = False) -> Iterable[pointLike]:
"""
Finds the closest point ON all of these objects TO the input shape.
PLEASE NOTE that this won't have the list in order of closest to furthest, you have to do that yourself.
Finds the closest point ON ANY of these objects TO the input shape.
Args:
othershape (Shape): The shape to find the cosest points towards
othershape (Shape): The shape to find the closest points towards
returnAll (bool, optional): Whether to return EVERY possible option, sorted from closest to furthest. Defaults to False.
Returns:
Iterable[pointLike]: All the closest point(s) ON each of these objects
"""
points = []
for s in self.shapes:
if returnAll:
if returnAll:
points = []
for s in self.shapes:
points.extend(s.closestPointTo(othershape, True))
else:
points.append(s.closestPointTo(othershape, False))
return points
def sortFunc(p):
op = othershape.closestPointTo(Point(*p), False)
return math.hypot(p[0]-op[0], p[1]-op[1])
return sorted(points, key=sortFunc)
else:
point = None
d = None
for s in self.shapes:
np = s.closestPointTo(othershape, False)
op = othershape.closestPointTo(Point(*np), False)
nd = math.hypot(np[0]-op[0], np[1]-op[1])
if d is None or nd < d:
d = nd
point = np
return point

def isContaining(self, othershape: Union[Shape,'Shapes',Iterable[Shape]]) -> bool:
"""
Expand All @@ -622,31 +637,29 @@ def isContaining(self, othershape: Union[Shape,'Shapes',Iterable[Shape]]) -> boo
if s.isContaining(othershape):
return True
return False

# TODO: Pick one method: either the dict or the list, not both (see below 2 funcs)

def isCorner(self, point: pointLike, precision: Number = BASEPRECISION) -> Dict[Union[Shape,'Shapes'], bool]:
def isCorner(self, point: pointLike, precision: Number = BASEPRECISION) -> bool:
"""
Takes each object and finds whether the input point is on the corner of that object.
Finds if the point is a corner on any of the objects.
Args:
point (pointLike): The point to find if it's on the corner or not
precision (Number, optional): The decimal places to round to to check. Defaults to 5.
Returns:
dict[Shape / Shapes: bool]: A dictionary of each object in this and whether the point is a corner on it or not.
bool: Whether the point is on a corner of any of the objects.
"""
cs = {}
for s in self.shapes:
cs[s] = s.isCorner(point, precision)
return cs
if s.isCorner(point, precision):
return True
return False

def tangent(self, point: pointLike, vel: pointLike) -> Iterable[Number]:
def tangent(self, point: pointLike, vel: pointLike) -> Iterable[Number]: # TODO: Make it return just one number
"""
Finds the tangent on each of these objects for the specified point. -90 = normal.
Args:
point (pointLike): The point to find the tangent from
point (pointLike): The point to find the tangent from.
vel (pointLike): Which direction the point is moving (useful for example with lines for finding which side of the line the tangent should be of)
Returns:
Expand All @@ -659,7 +672,25 @@ def tangent(self, point: pointLike, vel: pointLike) -> Iterable[Number]:

# TODO: handleCollisions

# TODO: to_points and to_lines
def toPoints(self) -> Iterable[pointLike]:
"""
Returns:
Iterable[pointLike]: Get a list of all the Points that make up this object. For a few shapes (e.g. circles), this will be empty.
"""
points = []
for s in self.shapes:
points.extend(s.toPoints())
return points

def toLines(self) -> Iterable['Line']:
"""
Returns:
Iterable[Line]: Get a list of all the Lines that make up this object. For anything under a ClosedShape, this will most likely be empty.
"""
lines = []
for s in self.shapes:
lines.extend(s.toLines())
return lines

def area(self) -> Number:
"""
Expand Down Expand Up @@ -1667,7 +1698,10 @@ def calculate(point):
vel = rotateBy0(vel, 180-diff*2)
vel = [vel[0]*closestObj.bounciness, vel[1]*closestObj.bounciness]
# HACK
smallness = rotateBy0([0, AVERYSMALLNUMBER], normal-diff)
angle = direction((0, 0), vel)-quart
qx = -math.sin(angle) * AVERYSMALLNUMBER
qy = math.cos(angle) * AVERYSMALLNUMBER
smallness = (qx, qy)
out, outvel = self.handleCollisionsPos(Circle(ThisClosestP[0]+smallness[0], ThisClosestP[1]+smallness[1], newCir.r),
Circle(*pos, oldCir.r), objs, vel, False, precision)
if replaceSelf:
Expand Down Expand Up @@ -1773,6 +1807,11 @@ def __str__(self):
class Arc(Circle):
"""A section of a circle's circumfrance. This is in the 'lines' group because it can be used as the outer edge of another shape.
This is defined as an x, y and radius just like a circle, but also with a start and end angle which is used to define the portion of the circle to take.
FIXME: **ARCS ARE VERY BROKEN BEWARNED**
TOFIX:
- Arc to arc get closest point when both end points are close to the middle of the other arc
- Circle-arc handle collisions when circle bouncing off the inside of the arc
**ANGLES ARE MEASURED IN DEGREES.**"""
GROUPS = {ShpGroups.LINES, ShpGroups.NOTSTRAIGHT}
Expand Down Expand Up @@ -2093,7 +2132,7 @@ def __str__(self):
class ClosedShape(Shape):
"""These are shapes like rects and polygons; if you split them into a list of lines all the lines join with one another.
Please do not use this class as it is just a building block for subclasses and to provide them with some basic methods."""
GROUPS = {ShpGroups.CLOSED}
GROUPS = {ShpGroups.CLOSED, ShpGroups.SPLITTABLE}
def _where(self, othershape: Shape) -> Iterable[pointLike]:
if not self.check_rects(othershape):
return []
Expand Down

0 comments on commit cf74464

Please sign in to comment.