Creating a better GetRotation function!
cbxOK, I have this code I have been using that I wrote my self some time ago (aka vb5 era) and have been using it ever since. It works,... but it's rather messy means of calculation and slow to boot!. I have left it alone until now (If it ain't broke don't fix it!) but now I would like to find a better algorithmic version of my GetRotation function. So my question or rather request is that if anyone out there can provide a function that can calculate the angle between two 2D points could you please post it on this thread so I can stop using this god awfull contraption I have been using for so long... Also the GetRotation function you provide must return a single that represents either a degree or a radian. It must also be contained within a single function and not call any outside methods. And lastly the code can be written in either vb5/6 or code. Although I prefer [:p] The code below was written in If anyone needs help understanding the code below just let me know and I will do my best to describe it. [code] Public Shared Function GetRotation(ByVal Source As Vector2, ByVal Destination As Vector2) As Single Dim Angle As Single Angle = GetAngle(Source, Destination) Return CorrectDegree(Source, Destination, Angle) End Function Public Shared Function GetSlope(ByVal Source As Vector2, ByVal Destination As Vector2) As Single Return Math.Abs(Destination.Y - Source.Y) / Math.Abs(Destination.X - Source.X) End Function Public Shared Function GetAngle(ByVal Source As Vector2, ByVal Destination As Vector2) As Single Dim Slope, Ang As Single Slope = GetSlope(Source, Destination) Ang = CSng(Math.Atan(Slope)) Return Ang End Function Public Shared Function CorrectDegree(ByVal Source As Vector2, ByVal Destination As Vector2, ByVal Angle As Single) As Single If Destination.Y < Source.Y Then If Destination.X < Source.X Then ' upleft Return RadianToDegree(Angle) + 180 Else ' upright Return -RadianToDegree(Angle) + 360 End If Else If Destination.X < Source.X Then ' low left Return -RadianToDegree(Angle) + 180 Else 'low right ' do nothing Return RadianToDegree(Angle) End If End If End Function[/code]
Eric ColemanYour code doesn't return the correct answer for the angle between two vectors. Here is a sample project that demonstrates the problem. If you use the vectors <1,0> and <1,1> your code generates an error, so I used <1,0> and <2,2>, which should return the same angle, and your code generates the wrong value. The sample program also shows another way of calculating the angle between two vectors and compares your funtion to one that I created. Download Attachment: [url=" Coleman/"][img]icon_paperclip.gif[/img][/url]
7.05 KB
game_makerSame as "Eric" Method [code] Public Function GetRotation(V1 As Vector2, V2 As Vector2) As Single Dim S As Single S = (V1.X * V2.X + V1.Y * V2.Y) / _ (Sqr(V1.X * V1.X + V1.Y * V1.Y) * Sqr(V2.X * V2.X + V2.Y * V2.Y)) GetRotation = Atn(Sqr(1 / (S * S) - 1)) * 57.29578 End Function [/code] the Idea behind this (vector calculations) [code] A.B = |A| |B| cost cost = (A.B) / (|A| |B|) t = arccos( (A.B) / (|A| |B|) ) and there is no arccos in vb.6 so we use arctan to got arccos : arccos = arctan(Sqr(1 / (X * X) - 1)) arctan named atn in vb but if you are using .Net then you can use arccos directly as built-in function [/code] regards [:)]
cbxUm,... I really don't know how to say this. Eric and Game_Maker, I don't think your GetRotation functions work. [?] You two oviously know more about advanced math then I do, but unless I'm not understanding something properly your GetRotation methods are not returning acurate rotation values. For example. Download erics GetRotation zip file and modify the form code to do the fallowing [code]Public L, R As Vector2 Private Sub frmTest_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove Select Case e.Button Case Windows.Forms.MouseButtons.Left L.X = e.X L.Y = e.Y Case Windows.Forms.MouseButtons.Right R.X = e.X R.Y = e.Y End Select With Me.CreateGraphics .Clear(Me.BackColor) .DrawLine(New Pen(Me.ForeColor), L.X, L.Y, R.X, R.Y) End With Me.Text = cbx.GetRotation(L, R).ToString & " - " & eric.GetRotation(L, R).ToString End Sub[/code] The code is written on but can be converted to vb6 easy enough. This is how I have been testing your GetRotation methods. By using the left and right mouse buttons to position a line on the form and then setting the forms title bar text to the values from your GetRotation methods. Now when when you run erics program you will see that my GetRotation method is returning proper rotational values from 0 to 359.999. But your guys GetRotation methods don't appear to be returning either degrees (0 to 359.999) or radians (0 to (PI * 2)). Download the attached video for a demonstration. Download Attachment: [url=""][img]icon_paperclip.gif[/img][/url]
67.36 KB
game_makerIt's really works very fine... It's depends on how do you want your angle ... I mean Eric code gives you the angle from (0,180) (witch you may want this type of angle) My first code returns the same type of angle too I have added another function that returns the angle with range (0,360) Download Attachment: [url=""][img]icon_paperclip.gif[/img][/url]
7.99 KB
cbxDamit! I screwed up my post I was making,... now I have to start all over. Soo much typing lost forever! Grrrr! [:(!][:(!][:(!] Anyway, turns out my code is actually 2x faster then both eric and game_makers when placed in a single method. My new code... [code] Public Function GetRotation(ByRef SX As Single, ByVal SY As Single, ByVal DX As Single, ByVal DY As Single) As Single Dim Slope As Single Dim Angle As Single Slope = System.Math.Abs(DY - SY) / System.Math.Abs(DX - SX) Angle = System.Math.Atan(Slope) If DY < SY Then If DX < SX Then ' upleft Return (Angle * 57.29578!) + 180 Else ' upright Return -(Angle * 57.29578!) + 360 End If Else If DX < SX Then ' low left Return -(Angle * 57.29578!) + 180 Else 'low right ' do nothing Return Angle * 57.29578! End If End If End Function[/code] ... Replace the code in the GetRotation project eric originally provided and replace my old GetRotation code with the new code provided above. So having said that are there any challengers out there who think they can make a faster GetRotation method[?][?][?] If so make a post!
game_makerBTW: Vector Calculus is a simple math subject [:)] You know that vectors are independent of space(x,y,z) It's only represent direction and magnitude Assume VecA = (4i,8j) = <4,8> , VecB = <8,4> [img][/img] The magnitude (length) of vecA = |vecA| = sqr(x^2 + y^2) = sqr(16+64) = 8.94 |VecB| = sqr(64+64) = 8.94 The Dot Product definition is: VecA (Dot product) VecB = |VecA| |VecB| cost Where t is the angle between them VecA (Dot product) VecB = VecA.X * VecB.X + VecA.Y * VecB.Y = 4 * 8 + 8 * 4 = 64 So cost = VecA . VecB / (|VecA| |VecB|) Therefore t = arccos( VecA . VecB / (|VecA| |VecB|) ) And 0 <= t <= 180 So t = arccos (64 / (8.94 * 8.94) = arccos(0.8) = 36.8 ____________________________ Let's find t using another method (Equation of Line) If we find the angle t1 between VecB and the X-Axis And The Angle t2 between VecA and the X-Axis The Subtract t2-t1 it will equal t .... Isn't it [:)] to find t1 we find the slope of line m1 then using atn() that's return t1 t1 = atn(m1) = atn(dy/dx) = atn(4/8) = atn(0.5) = 26.57 t2 = atn(m1) = atn(dy/dx) = atn(8/4) = atn(2) = 63.43 t = t2-t1 = 63.43-26.57 = 36.8 In gm module I used method1 In gm2 module I used method2 Regards [:)]
game_makerAre you tring to Get the rotation angle between to points and not 2 lines or vectors .... oops [:)] Because Me & Eric we ware sloving another problem [8)] hmmmmm .... this code is a bit faster [code] Public Function GetRotation(V1 As Vector2, V2 As Vector2) As Single Dim dX As Single, dY As Single dX = V2.X - V1.X dY = V2.Y - V1.Y GetRotation = Atn(dY / dX) * 57.29578 If dX < 0 Then GetRotation = GetRotation + (dY > 0) * 360 + 180 If GetRotation < 0 Then GetRotation = 360 + GetRotation End Function [/code] BTW : You should Put Condition if dx = 0 (slope is infnite)
cbx Yah, um, see all that fancy spread you did describing the equation of how to get rotation. You lost me even before you started typing it. [:D] Same goes for the graph image you displayed. The only way I have found for me to effectively understand math concepts in any coherent way is with code. Weather it be in C C++ pascal, basic, vb what ever, if it's code, I can understand it. If it's a math equasion -> "Eh, forget about it" [:p] Yes BTW we do appear to be describing 2 different things, What I wanted was the rotation value of 2 points in 2D space and you guys were giving me a rotation value using lines/vectors. [:0] "I see, said the blind man to the deaf man, who really was'nt listening anyway." [^]
Eric ColemanThe fact that you are passing a "vector" to your GetRotation function instead of a "point" is very misleading. The function I supplied is identical to game_maker's code except my function is optimized to minimize data type conversions. VB's math functions require Double data types as both input and output, so passing a data type of Single to the function and assinging it to a Single data type will mean there are 2 implicit data type conversions going on. Dim A As Single A = Sin(A) is the same as A = CSng(Sin(CDbl(A))) Visual Basic hides a LOT of data conversion for you, which makes programming simple and easy. However, if you do not know about this implicit Data Type conversion, then you can easily write sloppy code. Here is game_maker's function [code]Public Function GetRotation(V1 As Vector2, V2 As Vector2) As Single Dim S As Single S = (V1.X * V2.X + V1.Y * V2.Y) / _ (Sqr(V1.X * V1.X + V1.Y * V1.Y) * Sqr(V2.X * V2.X + V2.Y * V2.Y)) GetRotation = Atn(Sqr(1 / (S * S) - 1)) * 57.29578 End Function[/code] And here is mine, [code]Public Function GetRotation(V1 As Vector2, V2 As Vector2) As Single Dim p As Double, q As Double, r As Double Dim X1 As Double, X2 As Double, Y1 As Double, Y2 As Double Dim Ret As Single X1 = V1.X Y1 = V1.Y X2 = V2.X Y2 = V2.Y p = X1 * X2 + Y1 * Y2 q = Sqr(X1 * X1 + Y1 * Y1) * Sqr(X2 * X2 + Y2 * Y2) 'if q = 0 then this will not work. If q = 0 Then Exit Function r = p / q r = Atn(-r / Sqr(-r * r + 1)) + 1.5707963267949 GetRotation = CSng(r) * 57.29578! End Function[/code] game_maker's code uses ArcSin, which is wrong. But other than that, they're nearly identical. You will notice that I use temporary variables of type Double so that there are no hidden data type conversions from Single to Double or Double to Single. In fact, their are only 4 data type conversions, at the beginning of the function when I assign the variables X1, X2, Y1, Y2, and at the end when I explicitly convert "r" to be a number of type Single. I also multiply by 180 / pi, but I tell VB that it should treat it as a constant of type Single by using the "!". A double number uses #, Long is &, and Integer is %.
game_makerEric : You are really an advanced programmer , you know exactly what you are doing [8)] ... And about my first code (rotation between to vectors method 1) [code] Public Function GetRotation(V1 As Vector2, V2 As Vector2) As Single Dim S As Single, B1 As Byte, B2 As Byte S = (V1.X * V2.X + V1.Y * V2.Y) / _ (Sqr(V1.X * V1.X + V1.Y * V1.Y) * Sqr(V2.X * V2.X + V2.Y * V2.Y)) GetRotation = Atn(Sqr(1 / (S * S) - 1)) * 57.29578 If S < 0 Then GetRotation = 180 - GetRotation End Function [/code] 0 < theta < 180 It uses ArcCos And Not ArcSin .. because I am using [code] Atn(Sqr(1 / (S * S) - 1)) [/code] Witch is ArcCos ... because [code] tan(t) = x t = arctan(x) t= arctan(tan(t)) .... (1) Sin(t)^2 + Cos(t)^2 = 1 (equation of Unit Circle) devide by Cos(t)^2 tan(t)^2 + 1 = 1/cos(t)^2 tan(t)^2 = 1/cos(t)^2 - 1 tan(t) = sqr( (1/cos(t)^2 ) - 1 ) .... (2) using (1) into (2) t = arctan( (1/(cos(t)*cos(t) ) - 1 ) t = arctan( (1/(s*s)) - 1 ) [/code] so it's must be arccos .... and it gives 100% same result as your function gives ... [:)] cbx : yup ...sorry about the "Headache" [8)] ... I think I should wear [8D] from now on [:)]
Eric ColemanI didn't realize that your ArcCos function was different. I only noticed that you didn't add pi/2 to the result, so I assumed you were calculating ArcSin. I don't follow your derivation. I'm not sure what "s" is supposed to be in your last substitution from t = arctan( (1/(cos(t)*cos(t) ) - 1 ) => t = arctan( (1/(s*s)) - 1 ), which is missing a Sqrt from your original function, Atn(Sqr(1 / (S * S) - 1)). The function I used for ArcCos is from the VB help file, Inverse Sine, Arcsin(X) = Atn(X / Sqr(-X * X + 1)) Inverse Cosine, Arccos(X) = Atn(-X / Sqr(-X * X + 1)) + 2 * Atn(1) Here is another proof that I found online that is a bit easier to understand,
[code] C /| / | / | / | 1 / | / | / | sqrt(1-x^2) / | / | /_________| A B x [/code] Now, consider both the cosine and the tangent of angle A: cos A = x/1 = x tan A = (sqrt(1-x^2))/x Clearly, A = arccos(x) and also A = arctan((sqrt(1-x^2))/x). Therefore, arcos(x) = arctan((sqrt(1-x^2))/x). from
game_makerYa I forget about sqr() [8)] The derivation I wrote is easy one too... t = arctan ( tan(x) ) .... (1) and we want to find a function of cos(x) = tan(x) tan(x) = a function of cos(x) tan(x) = sqr( (1/ (cos(t)*cos(t) ) - 1 ) By (1) : t = arctan( sqr( (1/ (cos(t)*cos(t) ) - 1 ))) Let S = cos(t) t = arctan( sqr( (1/ (S*S ) - 1 ))) Your formula: arcos(x) = arctan((sqr(1-x^2))/x) and mine arcos(x) = arctan(sqr( (1/ (x*x ) - 1 ))) we can check mine if we equalize them arctan((sqr(1-x^2))/x) = arctan(sqr( (1/ (x*x ) - 1 ))) (sqr(1-x^2))/x = sqr( (1/ (x*x ) - 1 )) square both sides (1-(x*x))/(x*x) = (1/(x*x)) - 1 we take the left side (1-(x*x))/(x*x) = (1 / (x*x)) - (x*x)/(x*x) = (1 / (x*x)) - 1 = the right side BTW : I have a question about atn(arctan) ; how VB calculate this function at first place ..... I don't think that he used a series !! Maclaurian Series Says: arctan(x) = (-1)^n * ( x^(2*n - 1) / ( 2*n - 1 ) But using this in (for-loop) with precious counter will take very long time! [:)]
Eric ColemanI'm not sure how VB calculates functions like that either.