From 378302fee2f7a56c86a069374ea2e17038ff04aa Mon Sep 17 00:00:00 2001 From: "Tsunami014 (Max)" Date: Fri, 27 Dec 2024 09:56:59 +1100 Subject: [PATCH] =?UTF-8?q?fix(physics):=20=F0=9F=90=9B=20*Hopefully*=20FI?= =?UTF-8?q?XED=20THE=20lines-circle=20COLLISIONS!!!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now I have to fix the circle-X collisions. I hate circles --- BlazeSudio/collisions/lib/collisions.py | 103 +++++++++++++++--------- BlazeSudio/test/testfuncs.py | 30 ++++++- 2 files changed, 94 insertions(+), 39 deletions(-) diff --git a/BlazeSudio/collisions/lib/collisions.py b/BlazeSudio/collisions/lib/collisions.py index 4f3985c..617c9f9 100644 --- a/BlazeSudio/collisions/lib/collisions.py +++ b/BlazeSudio/collisions/lib/collisions.py @@ -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: @@ -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: @@ -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 @@ -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 @@ -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 @@ -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: @@ -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 @@ -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, @@ -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): """ @@ -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 @@ -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 diff --git a/BlazeSudio/test/testfuncs.py b/BlazeSudio/test/testfuncs.py index efefd34..4cf6e5b 100644 --- a/BlazeSudio/test/testfuncs.py +++ b/BlazeSudio/test/testfuncs.py @@ -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)) @@ -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)