Source code for BaseMod.props.RopePhysics

import math

from PySide6.QtCore import QPointF, QRectF, QPoint

from BaseMod.LevelParts import GeoLevelPart
from RWS.Loaders import Prop


[docs] def Diag(point1: QPointF, point2: QPointF): RectHeight = abs(point1.y() - point2.y()) RectWidth = abs(point1.x() - point2.x()) diagonal = math.sqrt((RectHeight * RectHeight) + (RectWidth * RectWidth)) return diagonal
[docs] def DiagWI(point1: QPointF, point2: QPointF, dig): RectHeight = abs(point1.y() - point2.y()) RectWidth = abs(point1.x() - point2.x()) return (RectHeight * RectHeight) + (RectWidth * RectWidth) < dig*dig
[docs] def MoveToPoint(pointA: QPointF, pointB: QPointF, theMovement): pointB = pointB - pointA diag = Diag(QPointF(0, 0), pointB) if diag > 0: dirVec = pointB * (1 / diag) else: dirVec = QPointF(0, 1) return dirVec * theMovement
[docs] def lerp(a, b, t): return (1 - t) * a + t * b
[docs] def restrict(a, minimum, maximum): return max(min(a, maximum), minimum)
[docs] class RopeModel:
[docs] def __init__(self, data: GeoLevelPart, pA: QPointF, pB: QPointF, prop: Prop, lengthFac, lr, rel): self.data: GeoLevelPart = data self.prop = prop.copy() self.lengthFac = lengthFac self.release = rel self.layer = lr self.posA: QPointF = pA self.posB: QPointF = pB self.segmentLength: int = prop["segmentLength"] self.grav = prop["grav"] self.stiff = prop["stiff"] self.segments = [] self.friction = prop["friction"] self.airFric = prop["airFric"] self.segRad = prop["segRad"] self.rigid = prop["rigid"] self.edgeDirection = prop["edgeDirection"] self.selfPush = prop["selfPush"] self.sourcePush = prop["sourcePush"] numberOfSegments = int(max((Diag(pA, pB) / self.segmentLength) * lengthFac, 3)) step = Diag(pA, pB) / numberOfSegments for i in range(1, numberOfSegments): self.segments.append({"pos": pA + MoveToPoint(pA, pB, (i - 0.5) * step), "lastPos": pA + MoveToPoint(pA, pB, (i - 0.5) * step), "vel": QPointF(0, 0) })
[docs] def modelRopeUpdate(self, collision=True): if self.edgeDirection > 0: dir = MoveToPoint(self.posA, self.posB, 1) if self.release > -1: for A in range(int(len(self.segments) / 2)): fac = pow(1 - ((A - 1) / (len(self.segments) / 2)), 2) self.segments[A]["vel"] += dir * fac * self.edgeDirection idealFirstPos = self.posA + dir * self.segmentLength self.segments[0]["pos"] = QPointF( lerp(self.segments[0]["pos"].x(), idealFirstPos.x(), self.edgeDirection), lerp(self.segments[0]["pos"].y(), idealFirstPos.y(), self.edgeDirection)) if self.release < 1: for A1 in range(1, int(len(self.segments) / 2)): fac = pow(1 - ((A1 - 1) / (len(self.segments) / 2)), 2) A = len(self.segments) - A1 self.segments[A]["vel"] -= dir * fac * self.edgeDirection idealFirstPos = self.posB - dir * self.segmentLength self.segments[-1]["pos"] = QPointF( lerp(self.segments[-1]["pos"].x(), idealFirstPos.x(), self.edgeDirection), lerp(self.segments[-1]["pos"].y(), idealFirstPos.y(), self.edgeDirection)) if self.release > -1: self.segments[0]["pos"] = self.posA.__copy__() self.segments[0]["vel"] = QPointF(0, 0) if self.release < 1: self.segments[-1]["pos"] = self.posB.__copy__() self.segments[-1]["vel"] = QPointF(0, 0) for i in range(len(self.segments)): self.segments[i]["lastPos"] = self.segments[i]["pos"] self.segments[i]["pos"] += self.segments[i]["vel"] self.segments[i]["vel"] *= self.airFric self.segments[i]["vel"] += QPointF(0, self.grav) for i in range(1, len(self.segments)): self.ConnectRopePoints(i, i-1) if self.rigid > 0: self.ApplyRigidity(i) for i in range(len(self.segments), 0): self.ConnectRopePoints(i, i+1) if self.rigid > 0: self.ApplyRigidity(i) if self.selfPush > 0: for A in range(len(self.segments)): for B in range(len(self.segments)): if A != B and DiagWI(self.segments[A]["pos"], self.segments[B]["pos"], self.selfPush): dir = MoveToPoint(self.segments[A]["pos"], self.segments[B]["pos"], 1) dist = Diag(self.segments[A]["pos"], self.segments[B]["pos"]) mov = dir * (dist-self.selfPush) self.segments[A]['pos'] += mov * 0.5 self.segments[A]['vel'] += mov * 0.5 self.segments[B]['pos'] -= mov * 0.5 self.segments[B]['vel'] -= mov * 0.5 if self.sourcePush > 0: for A in range(len(self.segments)): self.segments[A]["vel"] += MoveToPoint(self.posA, self.segments[A]["pos"], self.sourcePush) * restrict(((A - 1) / (len(self.segments) - 1)) - 0.7, 0, 1) self.segments[A]["vel"] += MoveToPoint(self.posB, self.segments[A]["pos"], self.sourcePush) * restrict((1 - ((A - 1) / (len(self.segments) - 1))) - 0.7, 0, 1) if collision: for i in range(int(self.release > -1), len(self.segments) - int(self.release < 1)): self.PushRopePointOutOfTerrain(i)
[docs] def ApplyRigidity(self, A): for B2 in [-2, 2, -3, 3, -4, 4]: B = A + B2 if 0 < B < len(self.segments): dir = MoveToPoint(self.segments[A]["pos"], self.segments[B]["pos"], 1) self.segments[A]["vel"] -= (dir * self.rigid * self.segmentLength) / (Diag(self.segments[A]["pos"], self.segments[B]["pos"]) + 0.1 + abs(B2)) self.segments[B]["vel"] += (dir * self.rigid * self.segmentLength) / (Diag(self.segments[A]["pos"], self.segments[B]["pos"]) + 0.1 + abs(B2))
[docs] def ConnectRopePoints(self, A, B): dir = MoveToPoint(self.segments[A]["pos"], self.segments[B]["pos"], 1) dist = Diag(self.segments[A]["pos"], self.segments[B]["pos"]) if self.stiff == 1 or dist > self.segmentLength: mov = dir * (dist - self.segmentLength) self.segments[A]['pos'] += mov * 0.5 self.segments[A]['vel'] += mov * 0.5 self.segments[B]['pos'] -= mov * 0.5 self.segments[B]['vel'] -= mov * 0.5
[docs] def PushRopePointOutOfTerrain(self, A): p = { "Loc": self.segments[A]["pos"].__copy__(), "LastLoc": self.segments[A]["lastPos"].__copy__(), "Frc": self.segments[A]["vel"].__copy__(), "SizePnt": QPointF(self.segRad, self.segRad).__copy__() } p = self.sharedCheckVCollision(p, self.friction, self.layer) self.segments[A]["pos"] = p["Loc"].__copy__() self.segments[A]["vel"] = p["Frc"].__copy__() gridPos = self.giveGridPos(self.segments[A]["pos"]) for dir in [QPointF(0, 0), QPointF(-1, 0), QPointF(-1, -1), QPointF(0, -1), QPointF(1, -1), QPointF(1, 0), QPointF(1, 1), QPointF(0, 1), QPointF(-1, 1)]: if self.afaMvLvlEdit(gridPos + dir, self.layer) == 1: midPos = self.giveMiddleOfTile(gridPos + dir) terrainPos = QPointF(restrict(self.segments[A]["pos"].x(), midPos.x() - 10, midPos.x() + 10), restrict(self.segments[A]["pos"].y(), midPos.y() - 10, midPos.y() + 10)) terrainPos = ((terrainPos * 10) + midPos) * (1 / 11) dir = MoveToPoint(self.segments[A]["pos"], terrainPos, 1) dist = Diag(self.segments[A]["pos"], terrainPos) - 1 if dist < self.segRad: mov = dir * (dist - self.segRad) self.segments[A]["pos"] += mov self.segments[A]["vel"] = QPointF(0, 0) # not a total fix
[docs] def giveMiddleOfTile(self, pos): return QPointF((pos.x() * 20) - 10, (pos.y() * 20) - 10)
[docs] def sharedCheckVCollision(self, p, friction, layer): bounce = 0 if p["Frc"].y() > 0: lastGridPos = self.giveGridPos(p["LastLoc"]) feetPos = self.giveGridPos(p["Loc"] + QPointF(0, p["SizePnt"].y() + 0.01)) lastFeetPos = self.giveGridPos(p["LastLoc"] + QPointF(0, p["SizePnt"].y())) leftPos = self.giveGridPos(p["Loc"] + QPointF(-p["SizePnt"].x() + 1, p["SizePnt"].y() + 0.01)) rightPos = self.giveGridPos(p["Loc"] + QPointF(p["SizePnt"].x() - 1, p["SizePnt"].y() + 0.01)) for q in range(int(lastFeetPos.y()), int(feetPos.y()) + 1): for c in range(int(leftPos.x()), int(rightPos.x()) + 1): if self.afaMvLvlEdit(QPointF(c, q), layer) == 1 and self.afaMvLvlEdit(QPointF(c, q-1), layer) != 1: if lastGridPos.y() >= q and self.afaMvLvlEdit(lastGridPos, layer) == 1: pass else: p["Loc"].setY(((q-1) * 20) - p["SizePnt"].y()) p["Frc"].setX(friction * p["Frc"].x()) p["Frc"].setY(-p["Frc"].y() * bounce) return p elif p["Frc"].y() < 0: lastGridPos = self.giveGridPos(p["LastLoc"]) headPos = self.giveGridPos(p["Loc"] - QPointF(0, p["SizePnt"].y() + 0.01)) lastHeadPos = self.giveGridPos(p["LastLoc"] - QPointF(0, p["SizePnt"].y())) leftPos = self.giveGridPos(p["Loc"] + QPointF(-p["SizePnt"].x() + 1, p["SizePnt"].y() + 0.01)) rightPos = self.giveGridPos(p["Loc"] + QPointF(p["SizePnt"].x() - 1, p["SizePnt"].y() + 0.01)) for d in range(int(lastHeadPos.y()), int(headPos.y()) + 1): q = (lastHeadPos.y()) - (d - headPos.y()) for c in range(int(leftPos.x()), int(rightPos.x()) + 1): if self.afaMvLvlEdit(QPointF(c, q), layer) == 1 and self.afaMvLvlEdit(QPointF(c, q+1), layer) != 1: if lastGridPos.y() <= q and self.afaMvLvlEdit(lastGridPos, layer) != 1: pass else: p["Loc"].setY((q * 20) + p["SizePnt"].y()) p["Frc"].setX(friction * p["Frc"].x()) p["Frc"].setY(-p["Frc"].y() * bounce) return p return p
[docs] def giveGridPos(self, pos: QPointF): return QPointF(int((pos.x() / 20) + 0.4999), int((pos.y() / 20) + 0.4999))
[docs] def afaMvLvlEdit(self, pos: QPointF, layer): # if __name__ == "__main__": # if pos.y > 11: # return 1 # return 0 if QRectF(0, 0, self.levelwidth - 1, self.levelheight - 1).contains(pos.toPoint() - QPoint(1, 1)): return self.data.getlevelgeo(int(pos.x() - 1), int(pos.y() - 1), layer)[0] return 1
@property def levelwidth(self): return self.data.width @property def levelheight(self): return self.data.height
# if __name__ == "__main__": # shit no longer works rn # import lingotojson # import pygame as pg # # data = lingotojson.turntoproject(open(Path("LevelEditorProjects") / "HI_C15.txt", "r").read()) # #rope = RopeModel(data, QPointF(60, 200), QPointF(60 + 9 * 16, 200), # # {"nm": "Wire", "tp": "rope", "depth": 4, "tags": [], "notes": [], "segmentLength": 10, # # "collisionDepth": 2, "segRad": 4.5, "grav": 0.5, "friction": 0.5, "airFric": 0.9, "stiff": 1, # # "previewColor": [255, 0, 0], "previewEvery": 4, "edgeDirection": 5, "rigid": 1.6, "selfPush": 0, # # "sourcePush": 0}, 1, 1, 0) # rope = RopeModel(data, QPointF(60, 200), QPointF(60 + 9 * 16, 200), # {"nm": "Wire", "tp": "rope", "depth": 0, "tags": [], "notes": [], "segmentLength": 3, # "collisionDepth": 0, "segRad": 1, "grav": 0.5, "friction": 0.5, "airFric": 0.9, "stiff": 0, # "previewColor": [255, 0, 0], "previewEvery": 4, "edgeDirection": 0, "rigid": 0, "selfPush": 0, # "sourcePush": 0}, 1, 1, 0) # timer = pg.time.Clock() # run = True # width = 1280 # height = 720 # window = pg.display.set_mode([width, height], flags=pg.RESIZABLE) # while run: # window.fill([0, 0, 0]) # for event in pg.event.get(): # match event.type: # case pg.QUIT: # exit(0) # if any(pg.key.get_pressed()): # rope.modelRopeUpdate() # for segment in rope.segments: # pg.draw.circle(window, [255, 0, 0], segment["pos"], 3) # pg.draw.line(window, [0, 255, 0], rope.posA, rope.posB) # pg.draw.line(window, [0, 0, 255], [0, 11*20], [width, 11*20]) # pg.display.flip() # pg.display.update() # timer.tick(20) # pg.quit() # exit(0)