Angle, Vector, Radian, and Distance Library
#1
While working on the tutorial site I decided the "Vectors, Angles & Rotation" lesson needed rewritten. Over the years I've had a few people point out that I was calculating these using methods much more difficult than needed. Also pointed out was some incorrect terminology I was using.

So, over the past week I went on a math quest to try and get these concepts correct. I decided to write a small library of routines to work with angles, vectors, and radians.

If you are mathematically inclined could you please take a moment to look over the code for any glaring errors I may have? Perhaps better ways of doing the math, my wording, etc.. I'm completely self-taught when it comes to higher math (Algebra II in high school) and don't always know the correct nomenclature to use. I believe I have it right this time from the week-long math brain-bender I went on.

These routines will be incorporated in the lesson and I plan to write a new lesson for creating and maintaining your own libraries using this one amongst others.

Code: (Select All)
'* Vector Demo - Tag, you're it!
'------------------------------------------------------------------------------------------------------------
' AVRDLibTop.BI
' Angle, Vector, Radian, and Distance Library
' By Terry Ritchie  quickbasic64@gmail.com
' 08/26/22
' Open source code - modify and distribute freely
' Original author's name must remain intact
'
' Declaration needed for the correct operation of the library.
'
TYPE XYPOINT '    2D point location definition
    x AS SINGLE ' x coordinate
    y AS SINGLE ' y coordinate
END TYPE
'------------------------------------------------------------------------------------------------------------

TYPE A_CIRCLE '                  circle definition
    location AS XYPOINT '        x,y coordinate
    vector AS XYPOINT '          x,y vector
    radius AS INTEGER '          radius
END TYPE

DIM RCircle AS A_CIRCLE '        a red circle
DIM GCircle AS A_CIRCLE '        a green circle
DIM Speed AS INTEGER '           speed of red circle
DIM Distance AS SINGLE '         distance between red and green circle
DIM Vector AS XYPOINT '          vector calculations
DIM Angle AS SINGLE '            angle calculations
DIM Radian AS SINGLE '           radian calculations

SCREEN _NEWIMAGE(640, 480, 32) ' graphics screen
_MOUSEHIDE '                     hide operating system mouse pointer
RCircle.location.x = 319 '       define circle properties
RCircle.location.y = 239
RCircle.radius = 30
GCircle.radius = 30
Speed = 3
DO '                                                               begin main program loop
    _LIMIT 60 '                                                    60 frames per second
    CLS
    P2PVector RCircle.location, GCircle.location, RCircle.vector ' vector from green to red circle
    Distance = P2PDistance(RCircle.location, GCircle.location) '   distance from center of circles
    Angle = P2PAngle(RCircle.location, GCircle.location) '         angle from center of circles
    Radian = Angle2Radian(Angle) '                                 radian calculated from angle
    Angle2Vector Angle, Vector
    PRINT "      Library Results     "
    PRINT " -------------------------"
    PRINT " P2PDistance   "; Distance
    PRINT " P2PAngle      "; Angle
    PRINT " Vector2Angle  "; Vector2Angle(Vector) '                angle calculated from vector
    PRINT " Radian2Angle  "; Radian2Angle(Radian) '                angle calculated from radian
    PRINT " Angle2Radian  "; Radian
    PRINT " Vector2Radian "; Vector2Radian(Vector) '               radian calculated from vector
    PRINT " Radian2Circle "; Radian2Circle(Radian) '               circle radian calculated from radian
    PRINT USING " P2PVector      Vx=##.##  Vy=##.##"; RCircle.vector.x; RCircle.vector.y
    PRINT USING " Angle2Vector   Vx=##.##  Vy=##.##"; Vector.x; Vector.y
    Radian2Vector Radian, Vector '                                 vector calculated from radian
    PRINT USING " Radian2Vector  Vx=##.##  Vy=##.##"; Vector.x, Vector.y
    LOCATE 2, 39: PRINT "TAG" '                                              print title of game
    IF Distance > GCircle.radius + RCircle.radius THEN '                     are circles colliding?
        RCircle.location.x = RCircle.location.x + RCircle.vector.x * Speed ' no, update enemy location
        RCircle.location.y = RCircle.location.y + RCircle.vector.y * Speed
    ELSE '                                                                   yes, circles colliding
        LOCATE 2, 35: PRINT "You're it!" '                                   print message to player
    END IF
    WHILE _MOUSEINPUT: WEND '                                                get latest mouse information
    GCircle.location.x = _MOUSEX '                                           update player location
    GCircle.location.y = _MOUSEY
    CIRCLE (GCircle.location.x, GCircle.location.y), GCircle.radius, _RGB32(0, 255, 0) '  draw player
    PAINT (GCircle.location.x, GCircle.location.y), _RGB32(0, 127, 0), _RGB32(0, 255, 0)
    CIRCLE (RCircle.location.x, RCircle.location.y), RCircle.radius, _RGB32(255, 0, 0) '  draw enemy
    PAINT (RCircle.location.x, RCircle.location.y), _RGB32(127, 0, 0), _RGB32(255, 0, 0)
    CIRCLE (RCircle.location.x, RCircle.location.y), 30, , -Radian2Circle(Radian), -Radian2Circle(Radian)
    _DISPLAY '                                                               update screen with changes
LOOP UNTIL _KEYDOWN(27) '                                                    leave when ESC pressed
SYSTEM '                                                                     return to operating system

'------------------------------------------------------------------------------------------------------------
' AVRDLib.BI
' Angle, Vector, Radian, and Distance Library
' By Terry Ritchie  quickbasic64@gmail.com
' 08/26/22
' Open source code - modify and distribute freely
' Original author's name must remain intact
'
' All subroutines and functions treat 0 degree and 0 radian as up and rotation is clock-wise.
' Some routines have dependencies on other routines.
'
' ** Subroutines **
'
' Angle2Vector  - converts the supplied degree angle (0 to 360) to a normalized vector
' P2PVector     - calculates the normalized vector between 2 points
' Radian2Vector - converts the supplied radian (0 to 2*PI) to a normalized vector

' ** Functions **
'
' Angle2Radian  - converts the supplied angle (0 to 360) to a radian (0 to 2*PI)
' FixRange      - ensures a value stays within a 0 to maximum range of values
' P2PDistance   - calculates the distance between 2 points
' P2PAngle      - calculates the angle (0 to 360) between 2 points
' Radian2Angle  - converts the supplied radian (0 to 2*PI) to a degree angle (0 to 360)
' Radian2Circle - converts the supplied radian (0 to 2*PI) to a radian (0 to 2*PI) to be used with the
'                 CIRCLE statement. The output radian adjusts the CIRCLE statement's radian so 0 is up
'                 and increasing radian values rotate in a clock-wise fashion.
' Vector2Angle  - converts the supplied normalized vector to a degree angle (0 to 360)
' Vector2Radian - converts the supplied normalized vector to a radian (0 to 2*PI)
'
' The following statements are needed at the top of the code for this library to function properly.
' (Use INCLUDE metastatement with AVRDLibTop.BI to include automatically.)
'
'  TYPE XYPOINT '    2D point location definition
'      x AS SINGLE ' x coordinate
'      y AS SINGLE ' y coordinate
'  END TYPE
'
'                                   0 Deg
'                                  0PI Rad                                   All functions and subroutines
'                                  *******                                   designed to treat 0 degrees,
'                             *****       *****                              0 radian, and vector 0,-1
'                         ****                 ****                          as up or north with values
'             315 Deg   **         Vec 0,-1        **   45 Deg               increasing clockwise.
'                     **              |              **
'                    *   Vec -1,-1    |     Vec 1,-1   *                     Radians supplied in the range
'                   *            \    |    /            *                    of 0 to 2 * PI
'                  *               \  |  /               *
'        1.5PI Rad *                 \|/                 * 1/2PI Rad         Degrees supplied in the range
'         270 Deg  * Vec -1,0 --------+--------- Vec 1,0 *  90 Deg           of 0 to 359.99..
'                  *                 /|\                 *
'                  *               /  |  \               *                   Vectors supplied in the range
'                   *            /    |    \            *                    of -1,-1 to 1,1 normalized
'                    *   Vec -1,1     |     Vec 1,1    *
'             225 Deg **              |              ** 135 Deg
'                       **         Vec 0,1         **
'                         ****                 ****
'                             *****       *****
'                                  *******
'                                  PI Rad
'                                  180 Deg
'
'------------------------------------------------------------------------------------------------------------

SUB P2PVector (P1 AS XYPOINT, P2 AS XYPOINT, V AS XYPOINT)

    '** NOTE: V passed by reference is altered

    '** Point to Point Vector Calculator
    '** Returns the x,y normalized vectors from P1 to P2

    ' P1.x, P1.y = FROM coordinate            (INPUT )
    ' P2.x, P2.y = TO coordinate              (INPUT )
    ' V.x, V.y   = normalized vectors to P2   (OUTPUT)

    DIM D AS SINGLE ' distance between points

    V.x = P2.x - P1.x '      horizontal distance (  side A  )
    V.y = P2.y - P1.y '      vertical distance   (  side B  )
    D = _HYPOT(V.x, V.y) '   direct distance (hypotenuse)
    IF D = 0 THEN EXIT SUB ' can't divide by 0
    V.x = V.x / D '          normalized x vector (  -1 to 1 )
    V.y = V.y / D '          normalized y vector (  -1 to 1 )

END SUB

'------------------------------------------------------------------------------------------------------------

FUNCTION P2PDistance (P1 AS XYPOINT, P2 AS XYPOINT)

    '** Point to Point Distance Calculator
    '** Returns the distance between P1 and P2

    ' P1.x, P1.y - FROM coordinate (INPUT)
    ' P2.x, P2.y - TO   coordinate (INPUT)
    ' returns SQR((P2.x - P1.x)^2 + (P2.y - P1.y)^2) using QB64 _HYPOT() function

    P2PDistance = _HYPOT(P2.x - P1.x, P2.y - P1.y) ' return direct distance (hypotenuse)

END FUNCTION

'------------------------------------------------------------------------------------------------------------

FUNCTION P2PAngle (P1 AS XYPOINT, P2 AS XYPOINT)

    '** Point to Point Angle Calculator
    '** Returns the degree angle from point 1 to point 2 with 0 degrees being up

    ' P1.x, P1.y - FROM coordinate (INPUT)
    ' P2.x, P2.y - TO   coordinate (INPUT)

    ' 57.29578 = 180 / PI

    DIM Theta AS SINGLE ' the returned degree angle

    IF P1.y = P2.y THEN '                                 do both points have same y value?
        IF P1.x = P2.x THEN '                             yes, do both points have same x value?
            EXIT FUNCTION '                               yes, identical points, no angle (0)
        END IF
        IF P2.x > P1.x THEN '                             is second point to the right of first point?
            P2PAngle = 90 '                               yes, the angle must be 90
        ELSE '                                            no, second point is to the left of first point
            P2PAngle = 270 '                              the amgle must be 270
        END IF
        EXIT FUNCTION '                                   leave function, angle calculated
    END IF
    IF P1.x = P2.x THEN '                                 do both points have the same x value?
        IF P2.y > P1.y THEN '                             yes, is second point below first point?
            P2PAngle = 180 '                              yes, the angle must be 180
        END IF
        EXIT FUNCTION '                                   leave function, angle calculated
    END IF
    Theta = _ATAN2(P2.y - P1.y, P2.x - P1.x) * 57.29578 ' calculate +/-180 degree angle
    IF Theta < 0 THEN Theta = 360 + Theta '               convert to 360 degree
    Theta = Theta + 90 '                                  set 0 degrees as up
    IF Theta > 360 THEN Theta = Theta - 360 '             adjust accordingly if needed
    P2PAngle = Theta '                                    return degree angle (0 to 359.99..)

END FUNCTION

'------------------------------------------------------------------------------------------------------------

FUNCTION Vector2Angle (V AS XYPOINT)

    '** Vector to Angle Calculator
    '** Converts the supplied normalized vector to a degree angle with 0 degrees facing up

    ' V.x, V.y - normalized vector (INPUT)

    ' 57.29578 = 180 / PI

    DIM Degrees AS SINGLE ' the returned degree angle

    Degrees = _ATAN2(V.y, V.x) * 57.29578 '         get angle from vector (-180 to 180)
    IF Degrees < 0 THEN Degrees = 360 + Degrees '   convert to 360
    Degrees = Degrees + 90 '                        set 0 degrees as up
    IF Degrees > 360 THEN Degrees = Degrees - 360 ' adjust if necessary
    Vector2Angle = Degrees '                        return degree angle (0 to 359.99..)

END FUNCTION

'------------------------------------------------------------------------------------------------------------

SUB Angle2Vector (A AS SINGLE, V AS XYPOINT)

    '** NOTE: V passed by reference is altered

    '** Angle to Vector Calculator
    '** Converts the supplied degree angle to a normalized vector

    ' A        - degree angle      (INPUT )
    ' V.x, V.y - normalized vector (OUTPUT)

    ' .017453292 = PI / 180

    DIM Angle AS SINGLE ' the angle value passed in

    Angle = A '                         don't alter passed in value
    IF Angle < 0 OR Angle >= 360 THEN ' angle outside limits?
        Angle = FixRange(Angle, 360) '  yes, correct angle
    END IF
    V.x = SIN(Angle * .017453292) '     return x vector
    V.y = -COS(Angle * .017453292) '    return y vector

END SUB

'------------------------------------------------------------------------------------------------------------

FUNCTION Vector2Radian (V AS XYPOINT)

    '** Vector to Radian Calculator
    '** Converts the supplied vector to a radian (0 to 2*PI)

    ' V.x, V.y - the supplied vector (INPUT)

    Vector2Radian = Angle2Radian(Vector2Angle(V)) ' return radian (0 to 2*PI)

END FUNCTION

'------------------------------------------------------------------------------------------------------------

SUB Radian2Vector (R AS SINGLE, V AS XYPOINT)

    '** NOTE: V passed by reference is altered

    '** Radian to Vector Calculator
    '** Converts the supplied radian to a normalized vector

    ' R        - supplied radian                (INPUT )
    ' V.x, V.y - the returned normalized vector (OUTPUT)

    ' 6.2831852 = 2 * PI

    DIM Radian AS SINGLE ' the radian value passed in

    Radian = R '                                don't alter passed in value
    IF Radian < 0 OR Radian >= 6.2831852 THEN ' radian outside limits?
        Radian = FixRange(Radian, 6.2831852) '  yes, correct radian
    END IF
    Angle2Vector Radian2Angle(Radian), V '      return normalized vector

END SUB

'------------------------------------------------------------------------------------------------------------

FUNCTION Radian2Angle (R AS SINGLE)

    '** Radian to Degree Angle Calculator
    '** Converts the supplied radian to an angle in degrees

    ' R - the supplied radian (INPUT)
    ' returns R * 180 / PI using the QB64 _R2D() function

    ' 6.2831852 = 2 * PI

    DIM Radian AS SINGLE ' the radian value passed in

    Radian = R '                                don't alter passed in value
    IF Radian < 0 OR Radian >= 6.2831852 THEN ' radian outside limits?
        Radian = FixRange(Radian, 6.2831852) '  yes, correct radian
    END IF
    Radian2Angle = _R2D(Radian) '               return angle (0 to 359.99..)

END FUNCTION

'------------------------------------------------------------------------------------------------------------

FUNCTION Angle2Radian (A AS SINGLE)

    '** Degree Angle to Radian Calculator
    '** Converts the supplied degree angle to radian

    ' A - the supplied degree angle (INPUT)
    ' returns A * PI / 180 using QB64 _D2R() function

    DIM Angle AS SINGLE ' the angle value passed in

    Angle = A '                         don't alter passed in value
    IF Angle < 0 OR Angle >= 360 THEN ' angle outside limits?
        Angle = FixRange(Angle, 360) '  yes, correct angle
    END IF
    Angle2Radian = _D2R(Angle) '        return radian (0 to 2*PI)

END FUNCTION

'------------------------------------------------------------------------------------------------------------

FUNCTION Radian2Circle (R AS SINGLE)

    '** Radian to CIRCLE Statement Radian Calculator
    '** Produces an output radian to be used with the CIRCLE statement that makes 0 up and
    '** the radian value increase clockwise instead of the CIRCLE statement's counter-clockwise default.

    ' R - the supplied radian (INPUT)

    ' 7.8539815 = 2.5 * PI
    ' 6.2831852 =   2 * PI

    DIM Radian AS SINGLE ' the radian value passed in

    Radian = R '                                             don't alter passed in value
    IF Radian < 0 OR Radian >= 6.2831852 THEN '              radian outside of limits?
        Radian = FixRange(Radian, 6.2831852) '               yes, correct radian
    END IF
    Radian = 7.8539815 - Radian '                            radian location on circle
    IF Radian > 6.2831852 THEN Radian = Radian - 6.2831852 ' adjust if necessary
    Radian2Circle = Radian '                                 return CIRCLE statement radian value

END FUNCTION

'------------------------------------------------------------------------------------------------------------

FUNCTION FixRange (N AS SINGLE, M AS SINGLE)

    '** Returns N within a fixed 0 to M range

    ' N - value to check
    ' M - maximim value N must be less than

    '** Code for this function provided by dcromley
    '** https://staging.qb64phoenix.com/showthread.php?tid=817
    '** 08/27/22

    FixRange = N - INT(N / M) * M ' rotate value as needed

END FUNCTION
Reply
#2
That's pretty cool! I couldn't check the accuracy but it's a great example.
Reply
#3
(08-27-2022, 01:24 AM)SierraKen Wrote: That's pretty cool! I couldn't check the accuracy but it's a great example.

Thank you for checking out it out.

I found a typo already. In the documentation containing the drawing of the circle I labeled the left radian as 3/4PI, it should be 1.5PI. Oops.
Reply
#4
Interesting!  Thanks.

You could replace a few 6-statement sequences with 1 statement.
It's kind of like a remainder, except the divisor can be non-integer.
I hope the commented code explains it.

Code: (Select All)
'---- The statements below
'  While x >= d ' x can be no bigger than d
'  x = x - d
'  Wend
'  While x < 0 ' x can be no less than 0
'    x = x + d
'  Wend
'---- Can be replaced by
'  x = x - int(x / d) * d

' for x = 1.3 and d = .5
Print 1.3 - Int(1.3 / .5) * .5 ' = .3 because int(1.3/.5) = 2 and .3 = 1.3 - 2 * .5

' for x = -.7 and d = .5
Print -.7 - Int(-.7 / .5) * .5 ' = .3 because int(-.7/.5) = -2 and .3 = -.7 - (-2) * .5
___________________________________________________________________________________
I am mostly grateful for the people who came before me.  Will the people after me be grateful for me?
Reply
#5
Good job.

I once did some benchmarking of the _HYPOT command and; in my tests, it seemed to work a little faster than SQR

D = _HYPOT(V.x, V.y)

instead of:

D = SQR(V.x * V.x + V.y * V.y)

It might be worth further investigation.
DO: LOOP: DO: LOOP
sha_na_na_na_na_na_na_na_na_na:
Reply
#6
Shouldn't 0 Degrees = 0 Radians be due East to match the Trig Functions in BASIC?

And you do know about _D2R and _R2D that convert Degrees to Radians and vice versa?

Code: (Select All)
Screen _NewImage(800, 600, 32)
Do
    Cls
    For a = 0 To 2 * _Pi - .01 Step _Pi(2 / 36)
        x = 400 + 200 * Cos(a)
        y = 300 + 200 * Sin(a)
        _PrintString (x, y), Str$(Int(_R2D(a) + .5))
    Next
    While _MouseInput: Wend
    mx = _MouseX: my = _MouseY
    a = _Atan2(my - 300, mx - 400)
    If a < 0 Then a = a + 2 * _Pi
    Line (400, 300)-(400 + 150 * Cos(a), 300 + 150 * Sin(a)), &HFFFFFF00
    _PrintString (388, 292), _Trim$(Str$(Int(_R2D(a) + .5)))
    _Display
    _Limit 60
Loop

   

Note: the Degrees labels are a little off center to circle center, it's just a quick demo.
b = b + ...
Reply
#7
Thanks for the replies. All great suggestions I need to look into.

Bplus: I purposely have the functions treat 0 degrees and 0 radians as north to match the coordinate system of the screen. Do you think it would be better if I give the programmer the option to choose where 0 degrees lies? Perhaps a CONST to be set such as ZERO=NORTH, ZERO=EAST, etc?

And no, I did not know about _D2R and _R2D. My goodness, I need to go through the command list and see what else I missed. I was away from QB64 for a while but it seems I missed a few key things. Did these commands come about around the same time _ATAN2?
Reply
#8
Quote:Do you think it would be better if I give the programmer the option to choose where 0 degrees lies? Perhaps a CONST to be set such as ZERO=NORTH, ZERO=EAST, etc?



It's not an arbitrary thing to just say 0 = North.  When you build a clock in Basic don't you have to subtract 90 degrees off all the angles to put 12 (= 0) at the North side of clock? Basic would have 12 o'clock pointed due East.

When you draw an equilateral triangle about a point x, y center, you have to subtract 90 degrees (or add 270 degrees) if you want the triangle pointed North instead of East.

Code: (Select All)
Screen _NewImage(800, 600, 32)

' draw triangle about 400, 300 at 100 radius
cx = 400
cy = 300
angle = 0 ' <<<< start angle at 0

x = cx + 100 * Cos(angle)
y = cy + 100 * Sin(angle)

anotherLine:
angle = angle + _Pi(2 / 3) ' add 120 degrees
newX = cx + 100 * Cos(angle)
newY = cy + 100 * Sin(angle)
Line (x, y)-(newX, newY)
lines = lines + 1
x = newX
y = newY
If lines < 3 Then GoTo anotherLine
Print "Press any for yellow triangle pointed North."
Sleep
Cls

' for triangle pointed north
angle = _Pi(3 / 2) ' <<<<  start angle at 270 degrees = 3*Pi/2
x = cx + 100 * Cos(angle)
y = cy + 100 * Sin(angle)
lines = 0

anotherLine2:
angle = angle + _Pi(2 / 3) ' add 120 degrees
newX = cx + 100 * Cos(angle)
newY = cy + 100 * Sin(angle)
Line (x, y)-(newX, newY), &HFFFFFF00
lines = lines + 1
x = newX
y = newY
If lines < 3 Then GoTo anotherLine2


In Basic, 0 degrees is due East of any given point x, y according to Basic Trig functions COS and SIN.

Cos(0) = 1, Sin(0) = 0 Heck that's not just Basic, Math says that too!
b = b + ...
Reply
#9
I updated the code in the original post with the suggested changes.

dcromley - I created a function using the code you provided called FixRange. Thank you.

Bplus: I see what you are stating. The reason I'm doing this is because these functions are geared toward the tutorial which has an emphasis on game programming.

The reason I didn't see the _R2D and _D2R is because they are not listed in the mathematics portion of the Wiki (by usage). I see they are listed however in the alphabetical listing so I'll need to check that more often. Thanks for the heads up guys on those commands and _HYPOT.
Reply
#10
Yeah Circle Function is wrong minded when it comes to arc drawing, it also often leaks when attempting PAINT of pie slices.

I recommend my arc drawing routine, it works correctly with the same angles used in Trig functions.
b = b + ...
Reply




Users browsing this thread: 5 Guest(s)