Task 13: Math Functions

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 (BASE

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 BASE

The Decimal or BASE_{10} Numbering System | ||||||

← to infinity | 100,000's Place | 10,000's Place | 1,000's Place | 100's Place | 10's Place | 1's Place |

← to infinity | 10^{5} | 10^{4} | 10^{3} | 10^{2} | 10^{1} | 10^{0} |

Chart 1 - BASE_{10} numbering system

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

(3 X 10

(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 BASE

- Binary (BASE

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 BASE

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 BASE_{2} Numbering System | ||||||||

← to infinity | 128's Place | 64's Place | 32's Place | 16's Place | 8's Place | 4's Place | 2's Place | 1's Place |

← to infinity | 2^{7} | 2^{6} | 2^{5} | 2^{4} | 2^{3} | 2^{2} | 2^{1} | 2^{0} |

Chart 2 - BASE_{2} numbering system

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

(1 X 2

( 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

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 1 | Input 2 | Output |

0 | 0 | 0 |

0 | 1 | 0 |

1 | 0 | 0 |

1 | 1 | 1 |

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 | ||||||||

1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | = 255 |

0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | = 64 |

1 AND 0 | 1 AND 1 | 1 AND 0 | 1 AND 0 | 1 AND 0 | 1 AND 0 | 1 AND 0 | 1 AND 0 | |

0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | = 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 | ||||||||

1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | = 237 |

1 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | = 212 |

1 AND 1 | 1 AND 1 | 1 AND 0 | 0 AND 1 | 1 AND 0 | 1 AND 1 | 0 AND 0 | 1 AND 0 | |

1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | = 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 1 | Input 2 | Output |

0 | 0 | 0 |

0 | 1 | 1 |

1 | 0 | 1 |

1 | 1 | 1 |

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 | ||||||||

1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | = 237 |

1 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | = 212 |

1 OR 1 | 1 OR 1 | 1 OR 0 | 0 OR 1 | 1 OR 0 | 1 OR 1 | 0 OR 0 | 1 OR 0 | |

1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | = 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 1 | Input 2 | Output |

0 | 0 | 0 |

0 | 1 | 1 |

1 | 0 | 1 |

1 | 1 | 0 |

Chart 8 - XOR Truth Table

The result of XORing 237 and 212 | ||||||||

1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | = 237 |

1 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | = 212 |

1 XOR 1 | 1 XOR 1 | 1 XOR 0 | 0 XOR 1 | 1 XOR 0 | 1 XOR 1 | 0 XOR 0 | 1 XOR 0 | |

0 | 0 | 1 | 1 | 1 | 0 | 0 | 1 | = 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 | |

Input | Output |

0 | 1 |

1 | 0 |

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:

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 |

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:

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