Skip to content

Commit

Permalink
fix(physics): 🐛 *Hopefully* FIXED THE lines-circle COLLISIONS!!!
Browse files Browse the repository at this point in the history
Now I have to fix the circle-X collisions.

I hate circles
  • Loading branch information
Tsunami014 (Max) authored and Tsunami014 (Max) committed Dec 26, 2024
1 parent 04a98ad commit 378302f
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 39 deletions.
103 changes: 67 additions & 36 deletions BlazeSudio/collisions/lib/collisions.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ class ShpGroups(Enum):
"""These shapes start in one spot and end in the same spot"""
LINES = 1
"""Shapes that make the outer edges of other shapes"""
GROUP = 2
NOTSTRAIGHT = 2
"""Shapes that are not straight; e.g. Circles"""
GROUP = 3
"""A group of other shapes"""

def checkShpType(shape: Union['Shape', 'Shapes'], typs: Union[Type, ShpGroups, Iterable[Union[Type, ShpGroups]]]) -> bool:
Expand Down Expand Up @@ -1180,6 +1182,11 @@ def handleCollisionsPos(self,
oldLine = Line(*sorted([oldLine.p1, oldLine.p2], key=lambda x: x[0]))
newLine = Line(*sorted([newLine.p1, newLine.p2], key=lambda x: x[0]))
mvement = Polygon(oldLine.p1, oldLine.p2, newLine.p2, newLine.p1)
# Don't let you move when you're in a wall
if oldLine.collides(objs):
if verbose:
return oldLine, [0, 0], [None, True]
return oldLine, [0, 0]
points = []
hit = False
for o in objs:
Expand All @@ -1195,11 +1202,6 @@ def handleCollisionsPos(self,
if verbose:
return newLine, vel, [None, False]
return newLine, vel
# Don't let you move when you're in a wall
if points == []:
if verbose:
return oldLine, [0, 0], [None, True]
return oldLine, [0, 0]
points.sort(key=lambda x: x[3])
closestP = points[0][0] # Closest point on the OTHER object
cPoint = points[0][2] # closestP projected onto the oldLine
Expand All @@ -1208,20 +1210,23 @@ def handleCollisionsPos(self,

thisNormal = math.degrees(math.atan2(oldLine[0][1]-oldLine[1][1], oldLine[0][0]-oldLine[1][0]))
paralell = False
cLine = None
thisIsOnP = oldLine.isCorner(cPoint, precision)
if checkShpType(closestObj, Line):
cLine = closestObj
elif checkShpType(closestObj, ShpGroups.CLOSED):
colllidingLns = [i for i in closestObj.toLines() if i.collides(Point(*closestP))]
if colllidingLns != []:
cLine = colllidingLns[0]
elif checkShpType(closestObj, Circle) and (not thisIsOnP):
paralell = True
if cLine is not None:
sortedOtherLn = Line(*sorted([cLine.p1, cLine.p2], key=lambda x: x[0]))
otherLnNormal = math.degrees(math.atan2(sortedOtherLn[0][1]-sortedOtherLn[1][1], sortedOtherLn[0][0]-sortedOtherLn[1][0]))
paralell = abs(otherLnNormal%360 - thisNormal%360) < precision or abs((otherLnNormal-180)%360 - thisNormal%360) < precision
if checkShpType(closestObj, ShpGroups.NOTSTRAIGHT):
paralell = not thisIsOnP
if not paralell:
cLine = None
if checkShpType(closestObj, Line):
cLine = closestObj
elif checkShpType(closestObj, ShpGroups.CLOSED):
colllidingLns = [i for i in closestObj.toLines() if i.collides(Point(*closestP))]
if colllidingLns != []:
cLine = colllidingLns[0]
elif checkShpType(closestObj, Circle) and (not thisIsOnP):
paralell = True
if cLine is not None:
sortedOtherLn = Line(*sorted([cLine.p1, cLine.p2], key=lambda x: x[0]))
otherLnNormal = math.degrees(math.atan2(sortedOtherLn[0][1]-sortedOtherLn[1][1], sortedOtherLn[0][0]-sortedOtherLn[1][0]))
paralell = abs(otherLnNormal%360 - thisNormal%360) < precision or abs((otherLnNormal-180)%360 - thisNormal%360) < precision
velDiff = 180
if paralell: # Line off line
collTyp = 3
Expand Down Expand Up @@ -1251,6 +1256,9 @@ def handleCollisionsPos(self,
#)
collTyp = None
normal, phi = 0, 0

if round(newPoint[0], precision) == round(closestP[0], precision) and round(newPoint[1], precision) == round(closestP[1], precision):
phi = normal+180

# the distance between the closest point on the other object and the corresponding point on the newLine
dist_left = math.hypot(newPoint[0]-closestP[0], newPoint[1]-closestP[1]) * closestObj.bounciness
Expand Down Expand Up @@ -1344,7 +1352,7 @@ class Circle(Shape):
"""A perfect circle. Defined as an x and y centre coordinate of the circle and a radius.
Please be mindful when checking for this class as it is technically a closed shape, but if you try to run \
`.toLines()` or `.toPoints()` it will return an empty list; so please check for it *before* closed shapes."""
GROUPS = [ShpGroups.CLOSED]
GROUPS = [ShpGroups.CLOSED, ShpGroups.NOTSTRAIGHT]
def __init__(self, x: Number, y: Number, r: Number, bounciness: float = BASEBOUNCINESS):
"""
Args:
Expand Down Expand Up @@ -1654,7 +1662,7 @@ def handleCollisionsVel(self,

def isCorner(self, point: pointLike, precision: Number = BASEPRECISION) -> bool:
"""
Finds whether a point is on a corner of this shape.
Finds whether a point is on a corner of this shape. But because circles don't have any corners, this will return False.
Args:
point (pointLike): The point to find if it's a corner
Expand Down Expand Up @@ -1721,7 +1729,7 @@ class Arc(Circle):
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.
**ANGLES ARE MEASURED IN DEGREES.**"""
GROUPS = [ShpGroups.LINES]
GROUPS = [ShpGroups.LINES, ShpGroups.NOTSTRAIGHT]
def __init__(self,
x: Number,
y: Number,
Expand Down Expand Up @@ -1963,6 +1971,23 @@ def rect(self) -> Iterable[Number]:
else:
W = max(eps[0][0], eps[1][0])
return E, N, W, S

def isCorner(self, point: pointLike, precision: Number = BASEPRECISION) -> bool:
"""
Finds whether a point is on a corner of this shape.
Args:
point (pointLike): The point to find if it's a corner
precision (Number, optional): The decimal places to round to to check. Defaults to 5.
Returns:
bool: Whether the point is on a corner of this shape
"""
for p in self.toPoints():
if round(p[0], precision) == round(point[0], precision) and \
round(p[1], precision) == round(point[1], precision):
return True
return False

def toPoints(self):
"""
Expand Down Expand Up @@ -2229,21 +2254,24 @@ def handleCollisionsPos(self,

thisNormal = math.degrees(math.atan2(oldLine[0][1]-oldLine[1][1], oldLine[0][0]-oldLine[1][0]))
paralell = False
cLines = []
thisIsOnP = oldLine.isCorner(cPoint, precision)
if checkShpType(closestObj, Line):
cLines = [closestObj]
elif checkShpType(closestObj, ShpGroups.CLOSED):
cLines = [i for i in closestObj.toLines() if i.collides(Point(*closestP))]
elif checkShpType(closestObj, Circle) and (not thisIsOnP):
paralell = True
if cLines != []:
for cLine in cLines:
sortedOtherLn = Line(*sorted([cLine.p1, cLine.p2], key=lambda x: x[0]))
otherLnNormal = math.degrees(math.atan2(sortedOtherLn[0][1]-sortedOtherLn[1][1], sortedOtherLn[0][0]-sortedOtherLn[1][0]))
paralell = abs(otherLnNormal%360 - thisNormal%360) < precision or abs((otherLnNormal-180)%360 - thisNormal%360) < precision
if paralell:
break
if checkShpType(closestObj, ShpGroups.NOTSTRAIGHT):
paralell = not thisIsOnP
else:
cLines = []
if checkShpType(closestObj, Line):
cLines = [closestObj]
elif checkShpType(closestObj, ShpGroups.CLOSED):
cLines = [i for i in closestObj.toLines() if i.collides(Point(*closestP))]
elif checkShpType(closestObj, Circle) and (not thisIsOnP):
paralell = True
if cLines != []:
for cLine in cLines:
sortedOtherLn = Line(*sorted([cLine.p1, cLine.p2], key=lambda x: x[0]))
otherLnNormal = math.degrees(math.atan2(sortedOtherLn[0][1]-sortedOtherLn[1][1], sortedOtherLn[0][0]-sortedOtherLn[1][0]))
paralell = abs(otherLnNormal%360 - thisNormal%360) < precision or abs((otherLnNormal-180)%360 - thisNormal%360) < precision
if paralell:
break
velDiff = 180
if paralell: # Line off line
collTyp = 3
Expand Down Expand Up @@ -2273,6 +2301,9 @@ def handleCollisionsPos(self,
#)
collTyp = None
normal, phi = 0, 0

if round(newPoint[0], precision) == round(closestP[0], precision) and round(newPoint[1], precision) == round(closestP[1], precision):
phi = normal+180

# the distance between the closest point on the other object and the corresponding point on the newLine
dist_left = math.hypot(newPoint[0]-closestP[0], newPoint[1]-closestP[1]) * closestObj.bounciness
Expand Down
30 changes: 27 additions & 3 deletions BlazeSudio/test/testfuncs.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ def testLine(testName, line, accel, shapes, expectedp1, expectedp2, expectedacce
# | |
# +--+

testLine('Line 9: Line v shape off circle exact',
((0, 0), (1.5, 0)), [1, 1], collisions.Shapes(collisions.Circle(2, 2, 1, 1)),
(1, 1), (2.5, 1), (-1, -1), 3)
#─-
# O

# Test circles
def testCircles(testName, inps, vel, collShps, expectedpos, expectedvel):
outcirc, outvel = collisions.Circle(*inps).handleCollisionsVel(vel, collisions.Shapes(*collShps))
Expand All @@ -148,16 +154,34 @@ def testCircles(testName, inps, vel, collShps, expectedpos, expectedvel):
)

testCircles('Circle 1: Rebound top',
(2, 0, 1), [0, 2], [collisions.Rect(0, 2, 4, 4, 1)],
(2, 0, 1), [0, 2], [collisions.Rect(0, 0, 4, 4, 1)],
(2, 0),
(0, -2)) # It is now going the opposite direction
(0, -3)) # It is now going the opposite direction
# o = current pos, N = new pos
# o
#+--+
#| N|
#| |
#+--+
# TODO: More tests
testCircles('Circle 2: Rebound side',
(0, 2, 1), [2, 0], [collisions.Rect(1, 0, 4, 4, 1)],
(0, 2), # It rebounded perfectly and now is exactly where it started
(-2, 0)) # It is now going the opposite direction
# o = current pos, N = new pos
# +--+
# | |
#o|N |
# +--+
testCircles('Circle 3: v shape',
(0, 0, 1), [2, 2], [collisions.Rect(0, 1, 4, 4, 1)],
(2, 0), # It rebounded like a v shape
(2, -2))
# o = current pos, N = new pos
#o
#+--+
#| N|
#| |
#+--+

# TIMING
shp1 = collisions.RotatedRect(0, 0, 1, 1, 45)
Expand Down

0 comments on commit 378302f

Please sign in to comment.