Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
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://qb64phoenix.com/forum/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.
  724  855  599  923  575  468  400  206  147  564  878  823  652  556 bxor cross forever
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!
  724  855  599  923  575  468  400  206  147  564  878  823  652  556 bxor cross forever
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.
  724  855  599  923  575  468  400  206  147  564  878  823  652  556 bxor cross forever
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Distance on aerthsurface Vincenty and Haversine formula in QB64 Rudy M 0 289 10-30-2025, 02:35 PM
Last Post: Rudy M
  QB UI Library aadityap0901 6 1,184 08-02-2025, 05:13 PM
Last Post: aadityap0901
  new approach to base conversion system and math library Dragoncat 2 577 07-16-2025, 10:19 PM
Last Post: Dragoncat
  Everything Date Library in progress SMcNeill 2 715 05-14-2025, 08:36 PM
Last Post: SMcNeill
  Text Menu Library Project Pete 3 728 01-03-2025, 05:55 PM
Last Post: Pete

Forum Jump:


Users browsing this thread: 1 Guest(s)