Angle, Vector, Radian, and Distance Library - TerryRitchie - 08-27-2022
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
RE: Angle, Vector, Radian, and Distance Library - SierraKen - 08-27-2022
That's pretty cool! I couldn't check the accuracy but it's a great example.
RE: Angle, Vector, Radian, and Distance Library - TerryRitchie - 08-27-2022
(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.
RE: Angle, Vector, Radian, and Distance Library - dcromley - 08-27-2022
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
RE: Angle, Vector, Radian, and Distance Library - OldMoses - 08-27-2022
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.
RE: Angle, Vector, Radian, and Distance Library - bplus - 08-27-2022
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.
RE: Angle, Vector, Radian, and Distance Library - TerryRitchie - 08-27-2022
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?
RE: Angle, Vector, Radian, and Distance Library - bplus - 08-27-2022
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!
RE: Angle, Vector, Radian, and Distance Library - TerryRitchie - 08-28-2022
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.
RE: Angle, Vector, Radian, and Distance Library - bplus - 08-29-2022
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.
|