Task 13  Task 13: Math Functions  Task 13


Let's make a 3D shooter!!

Before we delve into the wonderful world of QB64's math functions let's discuss a bit why math is important in programming, particularly game programming. When introducing programming to new students the first thing I usually hear is, "Let's create a game like (put the latest first person shooter here)!" Whoa there excited one, have you ever considered what is involved in these types of 3D games in terms of math? Just the act of projecting a 3D world onto a 2D screen requires at minimum a basic understanding of geometry, algebra, trigonometry and calculus. Then you need to think about the physics involved in these games. When you throw an object in a 3D world the object needs to respond as if it were in the real world. Things like gravity, friction and angular momentum come into play. To achieve these impressive results teams of programmers, skilled in the art of mathematics, create what is known as a physics engine. Then another team of programmers create the rendering and texture engines to bitmap images onto polygon surfaces. The most impressive part however is that these engines need to run in real time to get the best frames per second possible for fluid movement. 2D games such as side scrollers are not immune from math either. Gravity, friction, angular momentum and vector math typically come into play here along with collision detection routines that must monitor every moving object on the screen. A perfect example of a 2D game that is popular today that uses these complex math routines is Angry Birds that employs the free open source Box2D physics engine.

The object here is not to scare or intimidate you when it comes to math in game programming. The point I am trying to make is take math seriously and study as many fields of math and physics possible. Bottom line ... if you hate math you are probably not going to be a 3D game programmer. There are tricks to use in game programming that simulate complicated math, and you'll learn a few of them in this course. A good example of 3D trickery is Donkey Kong Country that was released for the Super Nintendo Entertainment System (SNES) in 1994. Every one was amazed at the 3D aspects of the game when it came out (it sold 9 million copies!!). In truth, most of it was faked. For example, the Donkey Kong character was pre-rendered in all possible positions using a 3D modeling tool such as MilkShape 3D. These rendered positions were then saved as sprites, or small individual images, onto a sprite sheet and then called as needed. Since the sprite images were already rendered in 3D the SNES had no real work to perform when displaying Donkey Kong in 3D. The SNES simply needed to access the images from memory and display them in the proper order. Movements were simply "mini-movies" of images placed quickly over top of each other to simulate the 3D effect. Many of today's most popular 2D games with some 3D elements use this same trick and it's one of the techniques you'll learn how to use in this course.

In order for computer based math to make sense however, you first need to have a basic understanding of how a computer counts and processes the numbers you give it.

Ok, 2D shooters for now!


- Decimal (BASE10) Numbering System -

Humans have been using a decimal based counting system for as long as time has been recorded and beyond. The decimal numbering system is based on 10 digits, 0 through 9, otherwise known as the BASE10 numbering system. BASE10 simply means that any digit to the left of any other digit is worth ten times as much in value. In school you knew these as place holders with names such as the 1's place, the 10's place, the 100's place and so on.

The Decimal or BASE10 Numbering System
← to infinity100,000's Place10,000's Place1,000's Place100's Place10's Place1's Place
← to infinity105104103102101100
Chart 1 - BASE10 numbering system

Using the chart above as an illustration the decimal number 387,237 can be broken down like this:

(3 X 105) + (8 X 104) + (7 X 103) + (2 X 102) + (3 X 101) + (7 X 100)
(300000)  +  (80000)  +  (7000)  +   (200)   +    (30)   +    (7)   = 387,237


In other words, each place holder is worth 10 times as much as the place holder to the right of it, hence the name BASE10. But why do humans use a numbering system based on 10s? Because it's something we are familiar with having 10 fingers and 10 toes. Imagine an early human holding up 7 fingers to convey how many antelope they saw to another tribe member, or perhaps displaying 10 fingers, closing the palms and then holding up 7 fingers to convey 17 antelope. Get the picture? Numbers were always displayed in groups of 10 and this evolved into the counting system we use today. If humans were born with 12 fingers and 12 toes we would be using the BASE12 numbering system today. Number systems are based off what the item, in this case a human, is capable of counting in because of what is available to count with (fingers and toes).

- Binary (BASE2 Numbering System) -

Computers at their core are based on the actions of transistors, simple electronic switches, turning on and off. Therefore, computers really only know two things, ON or OFF, and a numbering system that lends itself to two states needs to be used to describe this. That numbering system is known as binary, or BASE2, which only contains two digits, 0 and 1, where 0 is often referred to as OFF, or FALSE, and 1 referred to as ON, or TRUE. Once again, a numbering system needs to be used based off what the item, our computer, is capable of counting in.

The binary numbering system can be described exactly as the decimal numbering system except that each place holder is based on the number 2 instead of the number 10. Therefore each place holder is worth 2 times as much as the place holder to the right of it.


The Binary or BASE2 Numbering System
← to infinity128's Place64's Place32's Place16's Place8's Place4's Place2's Place1's Place
← to infinity2726252423222120
Chart 2 - BASE2 numbering system

Using the chart above as an illustration the binary number 10110111 can be broken down like this:

(1 X 27) + (0 X 26) + (1 X 25) + (1 X 24) + (0 X 23) + (1 X 22) + (1 X 21) + (1 X 20)
( 128 )  +  ( 0 )   +  ( 32 ) +  ( 16 )  +   ( 0 )  +   ( 4 )  +  ( 2 )   +  ( 1 )  = 183


Computers use binary numbers to represent the state in which transistors should be placed in. When the computer encounters a 1 it turns a transistor on and when it encounters a 0 it turns a transistor off. Placing a 1 at a video memory location, for instance, tells the video card to turn that pixel on. Computers today contain hundreds of millions of transistors and the interaction between them causes the magic to happen through programs placing 1s and 0s throughout locations in the computer called memory addresses. Also, in computing, certain lengths of bits are given names to identify how many bits are contained in the binary number as well.

Bit         -  A single 1 or 0
Nibble      -  4 bits
Byte        -  8 bits or 2 nibbles
Word        - 16 bits, 2 bytes or  4 nibbles
Double Word - 32 bits, 4 bytes or  8 nibbles
Quad Word   - 64 bits, 8 bytes or 16 nibbles

- Bitwise Math and Boolean Logic -

George Boole was a 19th century mathematician that devised a new form of mathematics based on logic and is often considered the founder of the field of computer science. Today we use his insights into logic, called Boolean logic, to describe the interactions between binary numbers and electronic states. Boolean logic is a perfect fit in computing because it can have only two states, true and false, and therefore only two outcomes, true or false. By equating the two digits of binary to the these states, 0 (zero) for false and 1 (one) for true, we can use George Boole's logic to perform what is known as bitwise math.

Bitwise math consists of operations that act on numbers at the bit level. Bitwise operations are often used for storing useful information and manipulating numbers at the bit level through the use of logic gates. Logic gates are used to sample bits, or inputs, and derive a single output in bit form. There are a number of  logic gate operators available in QB64 that allow the programmer to manipulate numbers at the bit, or binary, level.


- AND -

The first QB64 logical operator we'll discuss is AND which requires two inputs, or bits, to compute an output value as a bit. Logical operators can be described using a truth table as seen with AND in Chart 3 below:

AND Truth Table
Input 1Input 2Output
000
010
100
111
Chart 3 - AND Truth Table

The only time AND will return a 1, or true, is if both inputs are also a 1 or true. The truth table above can be viewed like so:

0 AND 0 = 0
0 AND 1 = 0
1 AND 0 = 0
1 AND 1 = 1

However, logical operators are rarely used with just one bit but instead many at once. Type the following code into your IDE and execute it.


b1% = 255
b2% = 64
PRINT b1% AND b2%

Why was 64 printed to the screen? What you need to remember is that logical operators work with all numbers at a binary level. Let's investigate what that last code snippet actually did. The decimal number 255 is 11111111 in binary and the decimal number 64 is 01000000 in binary. These two binary numbers need to be placed over top of each other and then AND will test the bits at each place holder. Chart 4 below shows this process:

The result of ANDing 255 and 64
11111111= 255
01000000= 64
1 AND 01 AND 11 AND 01 AND 01 AND 01 AND 01 AND 01 AND 0
01000000= 64
Chart 4 - ANDing 255 and 64 together

The only two place holders where there is a 1 in each column is located in the 64's place, therefore the answer to ANDing 255 and 64 is the number 01000000 in binary or 64 in decimal. Let's see another example:

The result of ANDing 237 and 212
11101101= 237
11010100= 212
1 AND 11 AND 11 AND 00 AND 11 AND 01 AND 10 AND 01 AND 0
11000100= 196
Chart 5 - ANDing 237 and 212 together

In this example the 128's, 64's and 4's place columns both contain 1s resulting in the binary number 11000100 or 196 in decimal.

Many times in QB64 source code you'll see code such as:

Variable% = 255
...
...
IF Variable% AND 64 THEN
    ...
    ...
END IF

Remember that the IF...END IF statement tests for truth and any non-zero value is considered true in the BASIC language. Therefore, if we substitute the value of 255 AND 64 into the line above we get:

IF 64 THEN

Since the decimal number 64 is not zero it equates to true as far as QB64 is concerned. How can code like this be useful? We'll get to that in a bit, first there are a few more logical operators to consider.


- OR -

The logical operator OR requires to input bits to test as well, resulting in the following truth table:

OR Truth Table
Input 1Input 2Output
000
011
101
111
Chart 6 - OR Truth Table

Here you can see that if one OR the other input bit is true the output is true. ORing the two numbers 237 and 212 would result in the following:

The result of ORing 237 and 212
11101101= 237
11010100= 212
1 OR 11 OR 11 OR 00 OR 11 OR 01 OR 10 OR 01 OR 0
11111101= 253
Chart 7 - ORing 237 and 212 together

OR can also be used to turn a bit on in a given number as well. Take the following code for example:

b1% = 128
b2% = 128
IF b2% AND 32 THEN
    PRINT "Bit 2^5 is turned on."
ELSE
    PRINT "Bit 2^5 is turned off."
END IF
b2% = b1% OR 32 ' ** turn bit 2^5 on **
IF b2% AND 32 THEN
    PRINT "Bit 2^5 is turned on."
ELSE
    PRINT "Bit 2^5 is turned off."
END IF

- XOR -

XOR, or eXclusive OR, will yield a true output if one input is true but not both. The truth table for XOR is shown in Chart 8 below:

XOR Truth Table
Input 1Input 2Output
000
011
101
110
Chart 8 - XOR Truth Table

The result of XORing 237 and 212
11101101= 237
11010100= 212
1 XOR 11 XOR 11 XOR 00 XOR 11 XOR 01 XOR 10 XOR 01 XOR 0
00111001= 57
Chart 9 - XORing 237 and 212 together

XOR can be used to turn bits off in a given number. Take the following code for example:

b1% = 128
b2% = 128
b2% = b1% OR 32 '  ** turn bit 2^5 on **
IF b2% AND 32 THEN
    PRINT "Bit 2^5 is turned on."
ELSE
    PRINT "Bit 2^5 is turned off."
END IF
b2% = b1% XOR 32 ' ** turn bit 2^5 off **
IF b2% AND 32 THEN
    PRINT "Bit 2^5 is turned on."
ELSE
    PRINT "Bit 2^5 is turned off."
END IF

- NOT -

The logical operator NOT takes only one input and inverts it resulting in an output that is the opposite of the input.

NOT Truth Table
InputOutput
01
10
Chart 9 - NOT Truth Table

Many times NOT is used to make a line of code easier to understand, like so:

PlayerDead% = 0 ' set player as alive (false)
...
...
IF NOT PlayerDead% THEN ' is player dead?
    ...                 ' no
    ...
END IF

The NOT logical operator in the line above inverts the value of the variable, turning what was a 0 into a 1 and therefore making the IF...THEN statement true.

- Bitwise Operation Examples -

There are many, many variables and conditions to track when writing computer games; is the player alive or dead, did the player grab the key needed to open this door, can the player move in a certain direction, etc. Many of these conditions can be set as true/false flags. Instead of creating a separate variable for each condition the information can be stored in the individual bits of a single variable.

A map can easily be created using bitwise operations to indicate which walls are closed and which are open to move through. Each cell of the map is given the following attributes:


Bitwise Use
Figure 1 - Setting Up a Map

By adding each binary placeholder with a corresponding wall a number can be created that indicates which walls are turned on. For example, the top left square contains the value of 11 because the North wall has a value of 1 (2^0), the East wall has a value of 2 (2^1) and the West wall has a value of 8 (2^3) that when added together equals 11. Bitwise operations can now be used to test for the existance of a wall in any given cell.

CONST NORTH = 1 '(2^0)
CONST EAST = 2 ' (2^1)
CONST SOUTH = 4 '(2^2)
CONST WEST = 8 ' (2^3)

CellValue% = 11

IF CellValue% AND NORTH THEN PRINT "North wall is present" ' test the first bit
IF CellValue% AND EAST THEN PRINT "East wall is present" '   test the second bit
IF CellValue% AND SOUTH THEN PRINT "South wall is present" ' test the third bit
IF CellValue% AND WEST THEN PRINT "West wall is present" '   test the fourth bit


Here is a demonstration program showing this concept in action. The player can use the arrow keys to move the red circle within the map however the player can't go through any walls that are present. The ESC key allows the player to leave the demonstration.

'*
'* Bitwise math demonstration
'*
'* Draws a simple map and allows player to move circle within map
'*

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

TYPE MAP '             set up map cell structure
    x AS INTEGER '     upper left x coordinate of cell
    y AS INTEGER '     upper left y coordinate of cell
    walls AS INTEGER ' identifies cell walls
END TYPE

DIM MAP(4, 4) AS MAP ' create the map array
DIM cx% '              current x cell coordinate of player
DIM cy% '              current y cell coordinate of player
DIM KeyPress$ '        player key presses

'----------------------------
'- Main Program Begins Here -
'----------------------------

SCREEN _NEWIMAGE(250, 250, 32) '                                  create 250x250 32bit screen
_TITLE "Simple Map" '                                             give window a title
CLS '                                                             clear the screen
DRAWMAP '                                                         draw the map
DO '                                                              MAIN LOOP begins here
    PCOPY 1, 0 '                                                  copy page 1 to current screen
    CIRCLE (MAP(cx%, cy%).x + 24, MAP(cx%, cy%).y + 24), 20, _RGB32(255, 0, 0) ' draw player
    PAINT (MAP(cx%, cy%).x + 24, MAP(cx%, cy%).y + 24), _RGB32(128, 0, 0), _RGB32(255, 0, 0)
    _DISPLAY '                                                    update the screen without flicker
    DO '                                                          KEY INPUT LOOP begins here
        KeyPress$ = INKEY$ '                                      get a key (if any) that player pressed
        _LIMIT 120 '                                              limit loop to 120 times per second
    LOOP UNTIL KeyPress$ <> "" '                                  KEY INPUT LOOP back if no key
    SELECT CASE KeyPress$ '                                       which key was pressed?
        CASE CHR$(27) '                                           the ESC key
            SYSTEM '                                              return to Windows
        CASE CHR$(0) + CHR$(72) '                                 the UP ARROW key
            IF NOT MAP(cx%, cy%).walls AND 1 THEN cy% = cy% - 1 ' move player up if no wall present
        CASE CHR$(0) + CHR$(77) '                                 the RIGHT ARROW key
            IF NOT MAP(cx%, cy%).walls AND 2 THEN cx% = cx% + 1 ' move player right if no wall present
        CASE CHR$(0) + CHR$(80) '                                 the DOWN ARROW key
            IF NOT MAP(cx%, cy%).walls AND 4 THEN cy% = cy% + 1 ' move player down if no wall present
        CASE CHR$(0) + CHR$(75) '                                 the LEFT ARROW key
            IF NOT MAP(cx%, cy%).walls AND 8 THEN cx% = cx% - 1 ' move player left if no wall present
    END SELECT
LOOP '                                                            MAIN LOOP back

'-----------------------------------
'- Function and Subroutine section -
'-----------------------------------

SUB DRAWMAP ()

'*
'* draws a map based on the value of each map cell
'*

SHARED MAP() AS MAP ' need access to map array

DIM x%, y% '          x,y map coordinates

FOR y% = 0 TO 4 '                                                 cycle through map rows
    FOR x% = 0 TO 4 '                                             cycle through map columns
        READ MAP(x%, y%).walls '                                  read wall DATA
        MAP(x%, y%).x = x% * 50 '                                 compute upper left x coordinate of cell
        MAP(x%, y%).y = y% * 50 '                                 compute upper left y coordinate of cell
        IF MAP(x%, y%).walls AND 1 THEN '                         is NORTH wall present?
            LINE (MAP(x%, y%).x, MAP(x%, y%).y)-(MAP(x%, y%).x + 49, MAP(x%, y%).y), _RGB32(255, 255, 255) ' yes, draw it
        END IF
        IF MAP(x%, y%).walls AND 2 THEN '                         is EAST wall present?
            LINE (MAP(x%, y%).x + 49, MAP(x%, y%).y)-(MAP(x%, y%).x + 49, MAP(x%, y%).y + 49), _RGB32(255, 255, 255) ' yes, draw it
        END IF
        IF MAP(x%, y%).walls AND 4 THEN '                         is SOUTH wall present?
            LINE (MAP(x%, y%).x, MAP(x%, y%).y + 49)-(MAP(x%, y%).x + 49, MAP(x%, y%).y + 49), _RGB32(255, 255, 255) ' yes, draw it
        END IF
        IF MAP(x%, y%).walls AND 8 THEN '                         is WEST wall present?
            LINE (MAP(x%, y%).x, MAP(x%, y%).y)-(MAP(x%, y%).x, MAP(x%, y%).y + 49), _RGB32(255, 255, 255) ' yes, draw it
        END IF
    NEXT x%
NEXT y%
PCOPY 0, 1 '                                                      save a copy of the map

END SUB

'------------------------
'- Program DATA section -
'------------------------

'*
'* Map cell values
'*

DATA 11,15,15,11,15,12,5,5,4,3,15,15,15,15,10,11,15,9,5,6,12,5,6,15,15

Simple Map
Figure 2 - The map demonstration program

- Basic Math Operations -

As discussed in Task 5, there are four basic math operations available in QB64; Addition ( + ), Subtraction ( - ), Multiplication ( * ) and Division ( / ). In addition to these there are three more basic math operations available to the programmer; Integer Division ( \ ), Remainder Division ( MOD ) and Exponent ( ^ ). The basic math functions adhere to the order of operations of math and they are:

- Math functions contained in parenthesis first ( )
- Exponent ( ^ )
- Multiplication ( * ) and division ( / ) to include remainder division ( MOD ) and integer division ( \ )
- Addition ( + ) and subtraction ( - )

Since Task 5 already explained the four basic math operations we'll focus on the three new ones.


- Integer Division ( \ ) -

Integer division \ will only return the whole number, or integer, portion of a result. This comes in handy in cases where only whole numbers are needed from a mthmatical statement, such as working with screen coordinates. The following lines of code highlight the difference between division and integer division:

PRINT 10 / 6 ' results in 1.6666 repeating
PRINT 10 \ 6 ' results in 1

Integer division will not round up but instead simply cuts the fractional part of the number off resulting in the return of the whole number. It's important to remember that integer division will always round the second number, known as the divisor, up or down accordingly. Therefore, if you try this:

I% = 10 \ .3

You will get a division by zero error because .3 was rounded down to zero. The divisor in integer division must always be greater than .5 to avoid a division by zero error.

- Remainder (Modulus) Division ( MOD ) -

The remainder, or Modulus, division function MOD will only return the remainder of a division operation as an integer. The following lines of code (pulled from the QB64 Wiki) highlights the differences between normal division, integer division and remainder division:

D! = 100 / 9
I% = 100 \ 9
R% = 100 MOD 9
PRINT "Normal Division:"; D!
PRINT "Integer Division:"; I%
PRINT "Remainder Division:"; R%

Normal division returns the value of 11.1111 repeating as it should and remainder division returns the value of 1. Integer division shows us a value of 11 meaning that 11 * 99 = 99 which therefore leaves a remainder of 1 as MOD has calculated for us.

It's important to remember that remainder division will always round the second number, known as the divisor, up or down accordingly. Therefore if you try this:

R% = 100 MOD .3

You will get a division by zero error because .3 was rounded down to zero. The divisor in remainder division must always be greater than .5 to avoid a division by zero error.


- Exponent ( ^ ) -

Exponent ^ is used to raise a numeric value to an exponential value. For example:

PRINT 2 ^ 3 ' results in 8

calculates 2 * 2 * 2 resulting in the value of 8. Exponents can also be used to calculate square and cubed roots of numbers like so:

PRINT 144 ^ (1 / 2) ' results in 12 as the square root ( 12 * 12 )
PRINT 27 ^ (1 / 3) '  results in 3  as the cubed  root ( 3 * 3 * 3 )


- QB64 Math Functions -

The BASIC language comes with a variety of mathmatical functions available to the programmer that are included with QB64. Think of mathmatical functions as predefined programs you can call upon to perform complicated math routines for you.

- INT() -

The INT() function rounds a numeric value down to the next whole number or integer. In other words, it returns the integer portion of a number. INT() rounds numbers down for both positive and negative numbers. Therefore:

PRINT INT(2.5) ' result is 2 rounded down

and

PRINT INT(-2.5) ' result is -3 rounded down

are both rounded down, although the negative rounding does appear odd. INT() comes in handy when converting between data types:

N! = 12.789 '  stored in a single variable type
C% = INT(N!) ' converted to integer and stored in an integer variable type


- CINT() & CLNG() -

The CINT() function is specifically used to convert decimal numbers to an integer. It does this using something called "Banker's Rounding", meaning that numbers with decimal points less than .5 are rounded down, numbers with decimal points higher than .5 are rounded up and numbers with .5 as the decimal value are rounded to the nearest even integer value, which may be up or down depending on the number. CINT() converts numbers to true intergers, meaning that you must remember to use numbers in the range of  32,767 to -32,768.

PRINT CINT(2.5) '  results in 2 as 2 is closest even number (round down)
PRINT CINT(3.5) '  results in 4 as 4 is the closest even number (round up)
PRINT CINT(10.6) ' results in 11 (round up)
PRINT CINT(10.4) ' results in 10 (round down)
N! = 1034.5 '      single value in integer range
I% = CINT(N!) '    results in 1034 as it is the closest even number (round down)


The CLNG() function is specifically used to convert a decimal number to a long integer. It uses the same "Banker's Rounding" scheme as CINT(). Since CLNG() converts to long integer values you must remember to use numbers in the range of 2,147,483,647 to -2,147,483,648.

- CSNG() & CDBL() -

CSNG() is used to convert a numeric value to the closest single precision number. This function is useful for defining a numeric value as single precision as well.

N# = 895.18741378374
S! = CSNG(N#)
PRINT S! ' results in the single precision number 895.1874

CDBL() is used to convert a numeric value to the closest double precision number. This function is mainly used to define any numeric value as double precision.


- _ROUND() -

_ROUND() is a new function offered by QB64 that rounds to the closest even integer, long integer or _integer64 value. This function can accept any numeric value to convert and performs the same functions as CINT() and CLNG() while offering conversion for numbers that excede long integer in size. With the introduction of _ROUND() there really is no need to use CINT() and CLNG() as _ROUND() makes a suitable substitute for both. _ROUND() uses the same "Banker's Rounding" scheme as CINT() and CLNG() as well.

- FIX() -

The FIX() function rounds a numerical value to the next whole number closest to zero, in effect truncating, or removing, the fractional portion of a number returning just the integer. This means that FIX() rounds down for positive numbers and up for negative numbers.

PRINT FIX(2.5) '  result is  2 printed to screen
PRINT FIX(-2.5) ' result is -2 printed to screen


- SQR() -

The SQR() function returns the square root of any positive numerical value.

N% = 256
PRINT SQR(N%) ' returns 16

Calculating the hypotenuse of a right triangle:

A% = 3 ' side A
B% = 4 ' side B
H! = SQR((A% ^ 2) + (B% ^ 2)) ' side C
PRINT "Hypotenuse:"; H! ' value of 5 is printed

Remember that SQR() will only work with positive numbers. Also the accuracy of SQR() is determined by the variable type being used to store the results.


- ABS() -

The ABS() function returns the absolute value of a numerical value, in effect turning negative numbers into positive numbers.

N% = -100
PRINT ABS(N%) ' results in 100 printed


- SGN() -

The SGN() function returns the sign of a numerical value. When the numerical value is less than zero -1 is returned, zero is returned if the numerical value is zero and 1 is returned for numerical values greater than zero.

N1% = -10
N2% = 0
N3% = 10
PRINT SGN(N1%), SGN(N2%), SGN(N3%)) ' -1  0  1  printed to screen


- SIN() & COS() -

The SIN() function returns the sine of an angle measured in radians. The following is an example that displays the seconds for an analog clock. (Adapted from code by Ted Weissgerber, aka Clippy, found in the QB64 Wiki.)

SCREEN 12
Pi2! = 8 * ATN(1) '                             2 * Pi
sec! = Pi2! / 60 '                              (2 * pi) / 60 movements per rotation
DO
    LOCATE 1, 1
    PRINT TIME$
    Seconds% = VAL(RIGHT$(TIME$, 2)) - 15 '     update seconds
    S! = Seconds% * sec! '                      radian from the TIME$ value
    Sx% = CINT(COS(S!) * 60) '                  pixel columns (60 = circular radius)
    Sy% = CINT(SIN(S!) * 60) '                  pixel rows
    LINE (320, 240)-(Sx% + 320, Sy% + 240), 12
    DO
        Check% = VAL(RIGHT$(TIME$, 2)) - 15
    LOOP UNTIL Check% <> Seconds% '             wait loop
    LINE (320, 240)-(Sx% + 320, Sy% + 240), 0 ' erase previous line
LOOP UNTIL INKEY$ = CHR$(27) '                  escape keypress exits

The COS() function returns the cosine of an angle in radians. The following code creates 12 anolog hour points that can be used with the clock example above. (Adapted from code by Ted Weissgerber, aka Clippy, found in the QB64 Wiki.)

PI2 = 8 * ATN(1) '                        2 * pi
arc! = PI2 / 12 '                         arc interval between hour circles
SCREEN 12
FOR t! = 0 TO PI2 STEP arc!
    cx% = CINT(COS(t!) * 70) '            pixel columns (circular radius = 70)
    cy% = CINT(SIN(t!) * 70) '            pixel rows
    CIRCLE (cx% + 320, cy% + 240), 3, 12
    PAINT STEP(0, 0), 9, 12
NEXT t!

- TAN() & ATN() -

The TAN() function returns the ratio of sine to cosine, or tangent value, of an angle measured in radians. Here is an example of SIN() and TAN() working together to create spiraling text. (Adapted from code by John Onyon, aka Unseen Machine, found in the QB64 Wiki.)

DIM SHARED text AS STRING
text$ = "S P I R A L"
DIM SHARED word(1 TO LEN(text$) * 8, 1 TO 16)
CALL analyse
CLS
CALL redraw

SUB analyse
CLS
SCREEN 12
COLOR 2: LOCATE 1, 1: PRINT text$
DIM px AS INTEGER, py AS INTEGER, cnt AS INTEGER, ltrcnt AS INTEGER
px = 1: py = 1
DO
    word(px, py) = POINT(px, py)
    PSET (px, py), 1
    px = px + 1
    IF px = LEN(text$) * 8 THEN
        px = 1
        py = py + 1
    END IF
LOOP UNTIL py = 16
END SUB

SUB redraw
CLS
DIM row AS INTEGER, cnt AS INTEGER, cstart AS SINGLE, cend AS SINGLE
DIM xrot AS INTEGER, yrot AS INTEGER, SCALE AS INTEGER, pan AS INTEGER
cstart = 0: cend = 6.2
xrot = 6: yrot = 6: SCALE = 3: pan = 30
OUT &H3C8, 1: OUT &H3C9, 10: OUT &H3C9, 10: OUT &H3C9, 60
DO
    row = 2
    DO
        DO
            FOR i = cend TO cstart STEP -.03
                x = (SCALE * 60 - (row * xrot / 4)) * TAN(COS(i))
                y = SIN(SCALE * 60 - (row * yrot)) * TAN(SIN(i)) * pan
                cnt = cnt + 1
                IF word(cnt, row) > 0 THEN
                    CIRCLE (x + 320, y + 220), SCALE + 1, 1 'circled letters
                    'LINE (x + 320, y + 220)-STEP(12, 12), 1, BF 'boxed letters
                END IF
                IF cnt = LEN(text$) * 8 THEN cnt = 0: EXIT DO
            NEXT
        LOOP
        row = row + 1
    LOOP UNTIL row = 16
    cend = cend + .1
    cstart = cstart + .1
    now! = TIMER
    DO
        newnow! = TIMER
    LOOP UNTIL newnow! - now! >= .15
    LINE (1, 100)-(639, 280), 0, BF
LOOP UNTIL INKEY$ = CHR$(27)
END SUB

The ATN() or arctangent function returns the angle in radians of a numerical tangent value. Here we use ATN() to find the angle from the mouse pointer to the center point of the screen. (Adapted from code by Rob, aka Galleon, found in the QB64 Wiki.)

SCREEN _NEWIMAGE(640, 480, 32)
x1! = 320
y1! = 240
DO
    PRESET (x1!, y1!), _RGB(255, 255, 255)
    dummy% = _MOUSEINPUT
    x2! = _MOUSEX
    y2! = _MOUSEY
    LINE (x1, y1)-(x2, y2), _RGB(255, 0, 0)
    LOCATE 1, 1: PRINT getangle(x1!, y1!, x2!, y2!)
    _DISPLAY
    _LIMIT 200
    CLS
LOOP UNTIL INKEY$ <> ""
END

FUNCTION getangle# (x1#, y1#, x2#, y2#) 'returns 0-359.99...
IF y2# = y1# THEN
    IF x1# = x2# THEN EXIT FUNCTION
    IF x2# > x1# THEN getangle# = 90 ELSE getangle# = 270
    EXIT FUNCTION
END IF
IF x2# = x1# THEN
    IF y2# > y1# THEN getangle# = 180
    EXIT FUNCTION
END IF
IF y2# < y1# THEN
    IF x2# > x1# THEN
        getangle# = ATN((x2# - x1#) / (y2# - y1#)) * -57.2957795131
    ELSE
        getangle# = ATN((x2# - x1#) / (y2# - y1#)) * -57.2957795131 + 360
    END IF
ELSE
    getangle# = ATN((x2# - x1#) / (y2# - y1#)) * -57.2957795131 + 180
END IF
END FUNCTION

- Advanced Functions -

Many other higher math functions can be derived from these basic functions offered. In fact, the QB64 Wiki maintains a listing of the most commonly used trigonomic functions that can be created from QB64's basic functions here. For the most part we'll use the basic math functions to perform most of our game magic in this course by creating custom functions for our needs. Two such functions may be to detect for a collision between objects on the screen using box areas:

FUNCTION BOXCOLLISION (Box1X!, Box1Y!, Box1Width!, Box1Height!, Box2X!, Box2Y!, Box2Width!, Box2Height!)

'**
'** Detects if two bounding box areas are in collision
'**
'** INPUT : Box1X!      - upper left corner X location of bounding box 1
'**         Box1Y!      - upper left corner Y location of bounding box 1
'**         Box1Width!  - the width of bounding box 1
'**         Box1Height! - the height of bounding box 1
'**         Box2X!      - upper left corner X location of bounding box 2
'**         Box2Y!      - upper left corner Y location of bounding box 2
'**         Box2Width!  - the width of bounding box 2
'**         Box2Height! - the height of bounding box 2
'**
'** OUTPUT: BOXCOLLISION - 0 (FALSE) for no collision, -1 (TRUE) for collision
'**

IF Box1X! <= Box2X! + Box2Width! THEN
    IF Box1X! + Box1Width! >= Box2X! THEN
        IF Box1Y! <= Box2Y! + Box2Height! THEN
            IF Box1Y! + Box1Height! >= Box2Y! THEN
                BOXCOLLISION = -1
            END IF
        END IF
    END IF
END IF

END FUNCTION

and round areas:

FUNCTION ROUNDCOLLISION (R1X!, R1Y!, R1R!, R2X!, R2Y!, R2R!)

'**
'** Detects if two circular areas are in collision by first checking the object's bounding box for a collision.
'** If a bounding box collision has occurred then a more accurate circular collision is checked for.
'**
'** INPUT : R1X! - the center X location of object 1
'**         R1Y! - the center Y location of object 1
'**         R1R! - the radius of object 1's bounding circle
'**         R2X! - the center X location of object 2
'**         R2Y! - the center Y location of object 2
'**         R2R! - the radius of object 2's bounding circle
'**
'** OUTPUT: ROUNDCOLLISION - 0 (FALSE) for no collision, -1 (TRUE) for collision
'**
'** USES  : BOXCOLLISION - used to check for bounding box collision first, for function speed.
'**

IF BOXCOLLISION(R1X! - R1R!, R1Y! - R1R!, 2 * R1R!, 2 * R1R!, R2X! - R2R!, R2Y! - R2R!, 2 * R2R!, 2 * R2R!) THEN
    IF SQR((R1X! - R2X!) ^ 2 + (R1Y! - R2Y!) ^ 2) < R1R! + R2R! THEN ROUNDCOLLISION = -1
END IF

END FUNCTION

- Moving Forward -

If this task has been a struggle for you to understand and comprehend that is ok. It simply means that you need to brush up on your current math skills and keep advancing your math skills as time goes on. The importance of obtaining higher math skills can't be emphasized enough if your goal is to become a game programmer. Even if you create games using graphics and physics engines by third party sources, you still need a basic understanding of the math involved to implement the engines properly.

High school math classes, in my opinion, do a horrible job of teaching the "why" behind math. In other words, why and where do these formulas and functions you learn get used in daily life? As a game programmer you suddenly discover the math in every day things because it needs to be applied to your game. Hopefully this course will help answer some of the "whys" you have been asking yourself all along.

-- Your Turn --










-- COMMAND REFERENCE --


Commands learned in previous tasks:

PRINT
INPUT
DIM
REM or '
CONST
TIME$
VAL()
IF...THEN
AND
END
GOTO
SELECT CASE...END SELECT
CASE
TO
IS
CASE ELSE
colon ( : )
FOR...NEXT
STEP
CLS
SLEEP
_DELAY
SYSTEM
DO...LOOP UNTIL (and variations)
WHILE...WEND
SCREEN
LINE
CIRCLE
PAINT
PSET
SUB
END SUB
FUNCTION
END FUNCTION
SHARED
SQR()
^ (exponent)
INPUT$
LINE INPUT
INKEY$
CHR$()
_KEYDOWN
_KEYHIT
_MOUSEX
_MOUSEY
_MOUSEINPUT
_MOUSEHIDE
_MOUSESHOW
_MOUSEMOVE
_MOUSEBUTTON
DIM
TYPE
END TYPE
AS
UCASE$()
LCASE$()
LTRIM$()
RTRIM$()
INSTR()
STR$()
DATE$
LEFT$()
RIGHT$()
MID$()
ASC()
STRING$()
SPACE$()
SWAP

New commands introduced in this task:

AND
OR
XOR
NOT
\ (integer division)
MOD
INT()
CINT()
CLNG()
CSNG()
CDBL()
_ROUND()
FIX()
ABS()
SGN()
SIN()
COS()
TAN()
ATN()

Concepts learned in previous tasks:

execute
statement
expression
literal string
syntax error
variables
type declarations
literal strings
concatenation
integers
long integers
single precision
double precision
strings
null strings
reserved words
operators ( +, -, *, / )
declare
constant
relational operators
functions
nesting
order of operations
Boolean
conditions
indenting
loops
labels
frame rate
controlled loops
conditional loops
Monochrome
Grayscale
pixels
bits per pixel
radian
counter-clockwise
aspect ratio
algorithm
subroutine
function
local variables
RAM
global variables
Pythagorean Theorem
BIOS
ASCII
buffer
CMOS
ROM
ASCII chart
array
one dimensional array
element
index
two dimensional array
three dimensional array
parse

New concepts introduced in this task:

geometry
algebra
trigonometry
calculus
angular momentum
physics engine
bitmap
polygon
vector math
numbering system
transistor
binary
binary numbering system
memory address
George Boole
Boolean logic
logic gate
bitwise math
flags
Banker's Rounding
sine
radian
cosine
tangent
arctangent