VB:Tutorials:Rotating A Point In 2D

From GPWiki
Jump to: navigation, search

This tutorial will discuss rotating a point around a point using Sin/Cos.

The Math

Sine and CoSine

What are Sine and Cosine? Sine and Cosine are our little buddies. They like to deal with angles. How do we rotate something with them? First we need a way to store points. Add this to a module:

Public Type POINT
    X As Single
    Y As Single
End Type
 
Public P As POINT

Rotating

Now we have a point. Useless! We need to rotate it. The math behind rotation (without matrix) is:


x' = Cos(Theta) * x - Sin(Theta) * y


y' = Sin(Theta) * x + Cos(Theta) * y

But remember this magical rule: Visual Basic uses radians! How do we convert degrees to radians? We need a constant PI, which equals Atn(1) * 4 which equals 3.14159265358979. Add that to the module. Now add this nifty little function:

Public Function D2R(ByVal Angle As Single) As Single
D2R = Angle / 180 * PI
End Function

Plug in Angle degrees and you get radians. Now we can add our formulas together (above) and get this:

Public Function RotatePoint(ByRef pPoint As POINT, ByVal Degrees As Single) As POINT
RotatePoint.X = Cos(D2R(Degrees)) * pPoint.X - Sin(D2R(Degrees)) * pPoint.Y
RotatePoint.Y = Sin(D2R(Degrees)) * pPoint.X + Cos(D2R(Degrees)) * pPoint.Y
End Function

Yeah! That's pretty cool, but also somewhat useless. Why? It always rotates around the origin! However, rotating around any point is easy.

Rotating around a point

Rotating around an origin is simple. The x/y terms in our Sin/Cos functions need to have the origin subtracted from them, resulting in a vector. The real coordinates may be obtained by adding the vector to the origin. Here is the code:

Public Function RotatePoint(ByRef pPoint As POINT, ByRef pOrigin As POINT, _ 
    ByVal Degrees As Single) As POINT
RotatePoint.X = pOrigin.X + ( Cos(D2R(Degrees)) * (pPoint.X - pOrigin.X) - _
    Sin(D2R(Degrees)) * (pPoint.Y - pOrigin.Y) )
RotatePoint.Y = pOrigin.Y + ( Sin(D2R(Degrees)) * (pPoint.X - pOrigin.X) + _
    Cos(D2R(Degrees)) * (pPoint.Y - pOrigin.Y) )
End Function

Now your point always rotates around the speicified origin!

Optimize

This section only applies if you will be constantly rotating points through out your game. By now, you probably know that the Sin and Cosine (and Tan and all other trig functions) are sssslllloooowwww. But how do we make them fast? Is there some other method of calculation? No! Instead we stop calculating at all! How do we do that? We create lookup tables. Add this to your module:

Public tSine(3599) As Single
Public tCoSine(3599) As Single

That's 0.0 to 359.9 degrees, so one decimal place of accuracy. Create a function called LoadTables, and add this:

Public Sub LoadTables()
  Dim i As Long
 
  For i = 0 To 3599
    tSine(i) = Sin(D2R(i / 10))
    tCoSine(i) = Cos(D2R(i / 10))
  Next i
End Sub

This goes through each element in the array, filling in data (in degrees). Now we need functions to get this data. You may be thinking something like this:

Public Function Sine(ByVal Angle As Single) As Single
  Sine = tSine(Angle*10)
End Function

This would work, but there is one flaw. What happens when the angle is too small or large? The array element won't exist! So we must fix that:

Public Function Sine(ByVal Angle As Single) As Single
  Dim A As Single
 
  A = Round(Angle, 1)
 
  A = ((A mod 360) + 360) mod 360
 
  Sine = tSine(A * 10)
End Function
 
Public Function CoSine(ByVal Angle As Single) As Single
  Dim A As Single
 
  A = Round(Angle, 1)
 
  A = ((A mod 360) + 360) mod 360
 
  CoSine = tCoSine(A * 10)
End Function

There! What this does is round the number to 1 decimal place. Then, it takes the remainder/Modulus, and corrects it for negatives. If you do not need 1 decimal place of accuracy (which is always good to have), you can use the more concise example below:

Public Function Sine(ByVal Angle As Long) As Single
  If Angle < 0 Then Angle = 360 + (Angle Mod 360)
  If Angle > 359 Then Angle = Angle Mod 360
 
  Sine = tSine(Angle)
End Function
 
Public Function CoSine(ByVal Angle As Long) As Single
  If Angle < 0 Then Angle = 360 + (Angle Mod 360)
  If Angle > 359 Then Angle = Angle Mod 360
 
  CoSine = tCoSine(Angle)
End Function
 
'I don't think the old way handled negatives properly... as a negative Mod 360 will be a negative number! whoops?

Just remember to make the arrays 0 to 359, instead of 0 to 3599. (Along with the initializing code!)

Now we must change the RotatePoint function to these changes. Notice how much cleaner it looks (and faster too!):

Public Function RotatePoint(ByRef pPoint As POINT, ByRef pOrigin As POINT, _ 
    ByVal Degrees As Single) As POINT
  RotatePoint.X = pOrigin.X + CoSine(Degrees) * (pPoint.X - pOrigin.X) - Sine(Degrees) * (pPoint.Y - pOrigin.Y)
  RotatePoint.Y = pOrigin.Y + Sine(Degrees) * (pPoint.X - pOrigin.X) + CoSine(Degrees) * (pPoint.Y - pOrigin.Y)
End Function

REMEMBER TO CALL THE LoadTable FUNCTION!!! While writing this code I kept thinking I had faulty code. The rotated point was always right on the origin! Then I realised I forgot to load the tables. So don't forget!

Done

Now you can rotate a point around any point! Here is the source code.

Instructions:
Right-click to set the origin point.
Left-click to set a point. Enter the angle in the dialog that pops up.
    The point will be rotated around the origin you selected.

Note: Since the cos of an angle is equal to the sin of (angle + 90) you only need a single array of lookups.

your CoSine function could simply be:

Public Function CoSine(ByVal Angle As Long) As Single
  CoSine = Sine(Angle + 90)
End Function

Then again this is the gameboy programmer in me where resources was a bit limited. We only ever used a single lookup table which we would put in rom and not in ram

See also

Rotating A Point In C#

Rotating A Point In 2D In Lua