Need Math Help - Sliding on a Line
BlitzMax Forums/BlitzMax Programming/Need Math Help - Sliding on a Line
| ||
So I'm trying to create a sliding collision system for a 2D top-down game. Basically I have a bunch of lines representing walls, and I've got a point representing the player. When the player hits a wall on an angle I want him to slide along the wall rather than just stick. So it seemed fairly straightforward at first. I have a good idea of what I need to do, but I'm having some trouble implementing it. Since I'm terrible at explaining things like this, I'll post a little diagram I made to represent what I'm trying to do: ![]() And here is the my current code: SuperStrict Type TPoint Field X:Float Field Y:Float Function Create:TPoint(X:Float, Y:Float) Local Point:TPoint = New TPoint Point.X = X Point.Y = Y Return Point EndFunction EndType Type TLine Field P1:TPoint Field P2:TPoint Method New() P1 = New TPoint P2 = New TPoint EndMethod Function Create:TLine(X1:Float, Y1:Float, X2:Float, Y2:Float) Local Line:TLine = New TLine Line.P1.X = X1 Line.P1.Y = Y1 Line.P2.X = X2 Line.P2.Y = Y2 Return Line EndFunction EndType Graphics(1024, 768, 1) Global g_ColPoint:TPoint = New TPoint Local LineList:TList = CreateList() LineList.AddLast(TLine.Create(100, 100, 200, 300)) LineList.AddLast(TLine.Create(300, 150, 400, 600)) Local PlayerPos:TPoint = New TPoint PlayerPos.X = 50 PlayerPos.Y = 50 Local PlayerOldPos:TPoint = New TPoint Local ColPoint:TPoint = New TPoint Repeat Cls PlayerOldPos.X = PlayerPos.X PlayerOldPos.Y = PlayerPos.Y If KeyDown(KEY_UP) PlayerPos.Y = PlayerPos.Y - 4.0 EndIf If KeyDown(KEY_DOWN) PlayerPos.Y = PlayerPos.Y + 4.0 EndIf If KeyDown(KEY_LEFT) PlayerPos.X = PlayerPos.X - 4.0 EndIf If KeyDown(KEY_RIGHT) PlayerPos.X = PlayerPos.X + 4.0 EndIf For Local L:TLine = EachIn LineList If LineIntersection(PlayerOldPos.X, PlayerOldPos.Y, PlayerPos.X, PlayerPos.Y, L.P1.X, L.P1.Y, L.P2.X, L.P2.Y) PlayerOldPos.X = PlayerOldPos.X - (PlayerPos.X - PlayerOldPos.X) PlayerOldPos.Y = PlayerOldPos.Y - (PlayerPos.Y - PlayerOldPos.Y) Local BackPoint:TPoint = New TPoint BackPoint.X = g_ColPoint.X + (PlayerPos.X - PlayerOldPos.X) BackPoint.Y = g_ColPoint.Y + (PlayerPos.Y - PlayerOldPos.Y) Local Slope:Float Slope = (L.P2.Y - L.P1.Y) / (L.P2.X - L.P1.X) Slope = -(1 / Slope) Local Ang:Float = ATan(Slope) Local NewPoint:TPoint = New TPoint NewPoint.X = BackPoint.X + Sin(Ang) * 6.0 NewPoint.Y = BackPoint.Y + Cos(Ang) * 6.0 PlayerPos.X = NewPoint.X PlayerPos.Y = NewPoint.Y Exit EndIf Next DrawRect(PlayerPos.X - 2, PlayerPos.Y - 2, 4, 4) DrawLineList(LineList) Flip Until KeyHit(KEY_ESCAPE) Function DrawLineList(List:TList) For Local L:TLine = EachIn List DrawLine(L.P1.X, L.P1.Y, L.P2.X, L.P2.Y) Next EndFunction Function LineIntersection:Int(XS1:Int, YS1:Int, XE1:Int, YE1:Int, XS2:Int, YS2:Int, XE2:Int, YE2:Int) Local XT1:Int Local YT1:Int Local XT2:Int Local YT2:Int If XS1 = XE1 Then XE1 = XS1 + 1 If XS2 = XE2 Then XE2 = XS2 + 1 Local xdif1:Float= XE1 - XS1 Local ydif1:Float = YE1 - YS1 Local m1:Float = ydif1 / xdif1 Local b1:Float = YS1 - XS1 * m1 Local xdif2:Float = (XE2-XS2) Local ydif2:Float = (YE2-YS2) Local m2:Float = ydif2 / xdif2 Local b2:Float = YS2 - XS2 * m2 Local x:Float = (b2 - b1) / (m1 - m2) g_ColPoint.X = x Local y:Float = m1 * x + b1 g_ColPoint.Y = y If XS1 > XE1 XT1 = XS1 XS1 = XE1 XE1 = XT1 EndIf If YS1 > YE1 YT1 = YS1 YS1 = YE1 YE1 = YT1 EndIf If XS2 > XE2 XT2 = XS2 XS2 = XE2 XE2 = XT2 EndIf If YS2 > YE2 YT2 = YS2 YS2 = YE2 YE2 = YT2 EndIf If x => XS1 If x <= XE1 If x => XS2 If x <= XE2 If y => YS1 If y <= YE1 If y => YS2 If y <= YE2 Return 1 EndIf EndIf EndIf EndIf EndIf EndIf EndIf EndIf Return 0 EndFunction You control the little box with the arrow keys. If you run that you'll notice that the box only slides along with line if it is going directly up into the line. If it's moving down, horizontally, or diagonally it goes right through it. Any help is appreciated. |
| ||
I've written this out about five times I think on this forum, so the answer you want is in the archives somewhere. If you can wait until I get back from France on Thursday I'll do a write-up on my blog. Your line intersection function is either wrong or not as simple as it could be, so google that, and to get a sliding effect you need to project the object's velocity vector onto the line it's colliding with, which you can also find on google. |
| ||
ah! Knew I'd find it: here |
| ||
Thanks very much, I've actually come up with a different method, using a circle instead of just a point for the player. Basically I check the distance from the center of the circle to each line segment, and if that distance is less than the circle's radius then it's colliding. If it's colliding then I change the velocity vector of the circle to be parallel with the line, then I multiply the velocity vector by the cosine of the difference between the angle of the line and the angle of the player, and this slows down the player depending on how directly he's facing the wall. Once again I'm pretty bad at explaining so I'll post my new code and you can take a look at it. SuperStrict Type TPoint Field X:Float Field Y:Float Function Create:TPoint(X:Float, Y:Float) Local Point:TPoint = New TPoint Point.X = X Point.Y = Y Return Point EndFunction EndType Type TLine Field P1:TPoint Field P2:TPoint Method New() P1 = New TPoint P2 = New TPoint EndMethod Function Create:TLine(X1:Float, Y1:Float, X2:Float, Y2:Float) Local Line:TLine = New TLine Line.P1.X = X1 Line.P1.Y = Y1 Line.P2.X = X2 Line.P2.Y = Y2 Return Line EndFunction EndType Graphics(1024, 768, 1) Global g_ColPoint:TPoint = New TPoint Global g_IntersectPoint:TPoint = New TPoint Local LineList:TList = CreateList() LineList.AddLast(TLine.Create(100, 100, 500, 100)) LineList.AddLast(TLine.Create(500, 100, 500, 500)) LineList.AddLast(TLine.Create(250, 100, 250, 500)) Local PlayerPos:TPoint = New TPoint PlayerPos.X = 350 PlayerPos.Y = 250 Local PlayerVel:TPoint = New TPoint Local PlayerOldPos:TPoint = New TPoint Local PlayerRadius:Float = 8.0 Local PlayerAng:Float = 0.0 Local ColPoint:TPoint = New TPoint Repeat Cls DrawText("Control the circle with the arrowkeys - UP = Move forward - DOWN = Move backward - RIGHT = Turn right - LEFT = Turn left", 10, 10) PlayerOldPos.X = PlayerPos.X PlayerOldPos.Y = PlayerPos.Y PlayerVel.X = 0.0 PlayerVel.Y = 0.0 If KeyDown(KEY_UP) PlayerVel.X = Sin(PlayerAng) * 4.0 PlayerVel.Y = Cos(PlayerAng) * 4.0 EndIf If KeyDown(KEY_LEFT) PlayerAng = PlayerAng + 4.0 EndIf If KeyDown(KEY_RIGHT) PlayerAng = PlayerAng - 4.0 EndIf If KeyDown(KEY_DOWN) PlayerVel.X = -Sin(PlayerAng) * 4.0 PlayerVel.Y = -Cos(PlayerAng) * 4.0 EndIf For Local L:TLine = EachIn LineList If DistanceToLineSegment(L.P1.X, L.P1.Y, L.P2.X, L.P2.Y, PlayerPos.X + PlayerVel.X, PlayerPos.Y + PlayerVel.Y) <= PlayerRadius Local LineAng:Float Local PlayerAng:Float Local AngDif:Float LineAng = ATan((L.P2.Y - L.P1.Y) / (L.P2.X - L.P1.X)) PlayerAng = ATan(PlayerVel.Y / PlayerVel.X) AngDif = PlayerAng - LineAng If PlayerVel.X > 0.0 PlayerVel.X = Cos(LineAng) * 4.0 * Cos(AngDif) PlayerVel.Y = Sin(LineAng) * 4.0 * Cos(AngDif) Else PlayerVel.X = Cos(LineAng) * 4.0 * -Cos(AngDif) PlayerVel.Y = Sin(LineAng) * 4.0 * -Cos(AngDif) EndIf EndIf Next PlayerPos.X = PlayerPos.X + PlayerVel.X PlayerPos.Y = PlayerPos.Y + PlayerVel.Y DrawCircle(PlayerPos.X, PlayerPos.Y, PlayerRadius) DrawLineList(LineList) Flip Until KeyHit(KEY_ESCAPE) Function DrawLineList(List:TList) For Local L:TLine = EachIn List DrawLine(L.P1.X, L.P1.Y, L.P2.X, L.P2.Y) Next EndFunction Function DistanceToLineSegment:Double(ax:Double,ay:Double, bx:Double,by:Double, px:Double,py:Double) 'Returns the distance from p to ' the closest point on line segment a-b. Local dx:Double=bx-ax Local dy:Double=by-ay Local t:Double = ( (py-ay)*dy + (px-ax)*dx ) / (dy*dy + dx*dx) If t<0 dx=ax dy=ay ElseIf t>1 dx=bx dy=by Else dx = ax+t*dx dy = ay+t*dy End If dx:-px dy:-py g_ColPoint.X = PX + DX g_ColPoint.Y = PY + DY Return Sqr(dx*dx + dy*dy) End Function Function DistanceToLine:Double(ax:Double,ay:Double, bx:Double,by:Double, px:Double,py:Double) 'Returns the distance from p to the closest point ' ont the line passing through a and b. Local dx:Double=bx-ax Local dy:Double=by-ay Return ( (ay-py)*dx + (px-ax)*dy ) / Sqr(dy*dy + dx*dx) EndFunction Function LineIntersection(line11x:Float, line11y:Float, line12x:Float, line12y:Float, line21x:Float, line21y:Float, line22x:Float, line22y:Float) 'Calculate intersection point. g_IntersectPoint.X = line11x + (((line22x-line21x)*(line11y-line21y)-(line22y-line21y)*(line11x-line21x))/((line22y-line21y)*(line12x-line11x)-(line22x-line21x)*(line12y-line11y)))*(line12x-line11x) g_IntersectPoint.Y = line11y + (((line22x-line21x)*(line11y-line21y)-(line22y-line21y)*(line11x-line21x))/((line22y-line21y)*(line12x-line11x)-(line22x-line21x)*(line12y-line11y)))*(line12y-line11y) EndFunction Function CCW:Int(P1:TPoint, P2:TPoint, P3:TPoint) Local DX1:Int Local DX2:Int Local DY1:Int Local DY2:Int DX1 = P2.x - P1.x DY1 = P2.y - P1.y DX1 = P3.x - P1.x DY2 = P3.y - P1.y; If dx1*dy2 > dy1*dx2 Return 1 EndIf If dx1*dy2 < dy1*dx2 Return -1 EndIf If (dx1*dx2 < 0) Or (dy1*dy2 < 0) Return -1 EndIf If (dx1*dx1 + dy1*dy1) < (dx2*dx2 + dy2*dy2) Return 1 EndIf Return 0 EndFunction Function DrawCircle(xCentre:Float, yCentre:Float, Radius:Float) DrawOval(xCentre - (Radius), yCentre - (Radius), Radius * 2, Radius * 2) EndFunction There are still quite a few problems with it--the circle occasionally gets stuck on outside corners, and sometimes it will just randomly disappear--so if you have a better method (and you probably do) then I'd appreciate it if you could do the write-up on your blog, although I'm still going to attempt to improve this one as well. Thanks! |