Lesson 20: Libraries
A library is a collection of useful tools that allow a programmer to develop code quickly and efficiently. A library provides pre-written snippets of code that a programmer can utilize over and over instead of recreating the wheel for each and every piece of software.
Imagine that you are creating software to perform conversions between various units of measures, weights, and temperatures. In lesson 2 we created a few programs of this type, but in order to do so we had to either know the conversion formulas or research their structure beforehand. The research, coding, and subsequent testing for accuracy of each formula can be very time consuming. It would be a great benefit if another programmer has already done this formula coding and offered the code for use. This is where libraries can be very beneficial. Let's assume another programmer did the coding and you found the library they offer. The library's name is "UnitConversions.BM" and all you need to do is add the library's code to your own with a statement like this:
'$INCLUDE:'UnitConversions.BM'
You now have all the unit conversion formulas offered in the library literally at your finger tips!
A QB64 library is nothing more than a .BAS source code file containing a collection of subroutines and functions. The file's extension is simply changed to .BM (or .BI, we'll get to that later) to identify the file as a library. Most programming languages offer methods of adding libraries to code and , in fact, most commercial and open source software written today relies heavily upon numerous libraries attached to the source code by the programmer. Most programming forums also include a dedicated area for the discussion and sharing of libraries with others. You can find the QB64 Phoenix Edition library discussion area here.
Building a Library
The best way to understand how a library functions is to build one. Let's create the library mentioned above called "UnitConversions.BM" using the unit conversion formulas from lesson 2. We'll then build a small program that utilizes the library to highlight how effective a library is in the hands of a programmer.
Start the IDE and type in the following code.
( This code can be found at .\tutorial\Lesson20\UnitConversions.BM )
'Unit Conversions Library
FUNCTION UC_F2C (f AS SINGLE)
' Convert Fahrenheit to Celsius and return result
UC_F2C = 5 * (f - 32) / 9
END FUNCTION
FUNCTION UC_C2F (c AS SINGLE)
' Convert Celsius to Fahrenheit and return result
UC_C2F = (c * 9 / 5) + 32
END FUNCTION
FUNCTION UC_mm2i (mm AS INTEGER)
' Convert millimeters to inches and return result
UC_mm2i = mm / 25.4
END FUNCTION
FUNCTION uc_i2mm (i AS SINGLE)
' Convert inches to millimeters and return result
uc_i2mm = i * 25.4
END FUNCTION
We now have a library with functions that convert between Celsius to Fahrenheit and millimeters to inches. Let's add this library to a program that allows the user to choose the desired conversion. Create a new listing in your IDE and type in the following program.
( This code can be found at .\tutorial\Lesson20\UnitConversionDemo.bas )
' Unit Conversion Library Demo
DIM KeyPress AS INTEGER ' user menu value selected
DIM v AS STRING ' value to convert
DO
CLS
LOCATE 2, 26: PRINT "Unit Conversion Library Demo" ' display menu
LOCATE 4, 21: PRINT "Select from the options below (1 to 5)"
LOCATE 7, 24: PRINT "1. Convert Fahrenheit to Celsius"
LOCATE 9, 24: PRINT "2. Convert Celsius to Fahrenheit"
LOCATE 11, 24: PRINT "3. Convert inches to millimeters"
LOCATE 13, 24: PRINT "4. Convert millimeters to inches"
LOCATE 15, 24: PRINT "5. Exit to the operating system"
DO ' begin key check loop
_LIMIT 30 ' don't overheat the CPU
KeyPress = VAL(INKEY$) ' get value of key press
LOOP UNTIL KeyPress > 0 AND KeyPress < 6 ' leave when value is 1 to 5
CLS
SELECT CASE KeyPress ' which menu option selected?
CASE 1 LOCATE 2, 25: PRINT "Convert Fahrenheit to Celsius"
LOCATE 4, 25: LINE INPUT "Enter Fahrenheit value> ", v
LOCATE 6, 25: PRINT "Result>"; STR$(UC_F2C(VAL(v)))
CASE 2
LOCATE 2, 25: PRINT "Convert Celsius to Fahrenheit"
LOCATE 4, 25: LINE INPUT "Enter Celsius value> ", v
LOCATE 6, 25: PRINT "Result>"; STR$(UC_C2F(VAL(v)))
CASE 3
LOCATE 2, 25: PRINT "Convert inches to millimeters"
LOCATE 4, 25: LINE INPUT "Enter inch value> ", v
LOCATE 6, 25: PRINT "Result>"; STR$(uc_i2mm(VAL(v)))
CASE 4
LOCATE 2, 25: PRINT "Convert millimeters to inches"
LOCATE 4, 25: LINE INPUT "Enter millimeter value> ", v
LOCATE 6, 25: PRINT "Result>"; STR$(UC_mm2i(VAL(v)))
CASE 5 ' user chose to exit
SYSTEM ' return to operating system
END SELECT
LOCATE 12, 24: PRINT "Press any key to return to menu"
SLEEP
LOOP
'$INCLUDE:'UnitConversions.BM'
Looking at the code listing above you'll notice that the functions UC_F2C(), UC_C2F, UC_i2mm, and UC_mm2i have all been highlighted in green by the IDE identifying them as custom functions. By including the library on line 44 the IDE considers the functions contained within the library simply part of the main body code.
There are two types of library files that can be created and included within your code. The first one, .BM files, only contain QB64 functions and subroutines. Library files ending in .BM must be placed at the end of your code since the IDE expects all functions and subroutines to appear after the main body. The second type of library files end in .BI. Library files ending in .BI contain variable and type declarations. Since variables declared with DIM, constants created with CONST, and type definitions created with TYPE ... END TYPE need to be at the beginning of code, so do .BI library files.
If you were to try and include variable declarations, type definitions, and/or constants within the same library file as the functions and subroutines the IDE would complain and generate an error. The need to create two different library files is not a common practice amongst programming languages. QB64 requires this behavior simply because the IDE does not have the capability of sorting out which statements go where. Perhaps in the future the IDE will be updated to support everything placed into one file, but for now you'll need to remember most libraries will require two separate files, a .BI that gets placed at the top of the code and a .BM file that gets placed at the bottom of the code. We'll create another library down below that uses both types of files so you can see this in action.
NOTE: Library files are not required to end in .BI and .BM. This naming convention seems to be the most common amongst most QB64 programmers. In fact, back in 2011 when I started writing QB64 libraries I named the top file .TOP and the bottom file .LIB. However, since the convention is overwhelmingly .BI and .BM this tutorial will stick with this naming convention to avoid confusion.
Unwritten Rules
Beyond the requirement for .BI and .BM files there are a few other rules that should be followed when writing libraries. Keep in mind these "rules" are merely suggestions that will help you and others when utilizing your libraries.
Preface variables, subs, and functions exposed to the main body of code with a unique identifier.
You may have noticed that each function in the UnitConversions.BM file above was prefaced with UC_, such as UC_F2C(). The reason for this is two-fold. First, when each of the functions are used in code the UC_ preface identifies that function as belonging to the unit conversion library. Secondly, it highly reduces the possibility of a library subroutine, function, or variable name as having the same name as a subroutine, function, or variable in the main body code. If you were to create a library that contained an exposed variable named TaxRate, for example, there is a high probability that someone else that uses your library may create a variable with the same name. By naming the variable something like IRSL_TaxRate the probability of the programmer using the same variable name is greatly reduced.
Write your library to be as self sufficient as possible.
Try to avoid reliance on external variables as much as possible. Passing variables into subroutines and functions through parameters is preferential. For example, many QB64 programmers set up boolean truth detectors like so:
If you write your subroutines and functions to rely on the constants TRUE and FALSE you'll need to include them in the .BI file. This can cause confusion when the programmer adds your library and an error is generated about a duplicate constant being created either right away or when the programmer creates those commonly used constants later on. Instead of checking for or setting TRUE and FALSE check for or set 0 and -1 instead:
The upcoming second library example will demonstrate this in more detail.
Document, document, document!
Create documentation for your libraries. I can't stress this enough. That nice shiny library you just created is working perfectly and only contains 20 statements, or commands, to remember. Two months later you'll have completely forgotten how to use those shiny new commands, trust me. Without documentation you'll either be forced to review the entire source code for the library again or look at past projects you created with the library to remember how to use it. If you create libraries that you intend to share with others and don't include documentation on its use then it's useless to other programmers. Documenting libraries is very time consuming. You're going to skip this step because of this. You'll be sorry you did at some point and then remember the words of wisdom you gleaned here.
Include sample source code
If you plan to distribute your library to others always include sample programs showing how to implement your library's functions and subroutines.
Collision Detection Library
Let's build a library that utilizes all of the suggested rules above and as a bonus will greatly aid in the development of your games. In lesson 15 you were introduced to four methods of collision detection; rectangular, circular, line intersection, and pixel perfect. Combining all of these detection routines into one library will save much time and effort when writing your games.
The first thing to consider is the .BI file. Which variables and type definitions do we absolutely need and how can they be designed to be used easily between the library's functions and procedures?
( This code can be found at .\tutorial\Lesson20\CollisionLibrary.BI )
' Collision Library V1.0
' Terry Ritchie 01/02/23
' CollisionLibrary.BI
' To be placed at the top of the main source code before variable and type definitions
'
TYPE CL_POINT ' 2D point definition
x AS INTEGER ' x coordinate
y AS INTEGER ' y coordinate
END TYPE
TYPE CL_CIRCLE ' circle definition
Center AS CL_POINT ' center of circle
Radius AS INTEGER ' circle radius
END TYPE
TYPE CL_RECTANGLE ' 2D rectangle definition
TopLeft AS CL_POINT ' top left x,y location
BottomRight AS CL_POINT ' bottom right x,y location
Width AS INTEGER ' rectangle width
Height AS INTEGER ' rectangle height
END TYPE
TYPE CL_SPRITE ' sprite definition
Image AS LONG ' sprite image
Mask AS LONG ' sprite mask image
TopLeft AS CL_POINT ' top left x,y location
BottomRight AS CL_POINT ' bottom right x,y location
END TYPE
TYPE CL_LINE ' 2D line segment definition
Start AS CL_POINT ' x,y start of line
Finish AS CL_POINT ' x,y end of line
END TYPE
TYPE CL_INTERSECT ' intersect collision points
Line AS CL_POINT ' 2D point location of line intersection
Rectangle AS CL_RECTANGLE ' rectangular area of rectangle intersection
Circle AS CL_POINT ' 2D point location of circle intersection
Pixel AS CL_POINT ' 2D point location of pixel perfect intersection
END TYPE
DIM CL_Intersect AS CL_INTERSECT ' intersection of collisions
Now it's time for the .BM file which contains the subroutines and functions at the heart of the library.
( This code can be found at .\tutorial\Lesson20\CollisionLibrary.BM )
' Collision Library V1.0
' Terry Ritchie 01/02/23
' CollisionLibrary.BM
' To be placed at the bottom of the main source code after the subroutines and functions
'
'------------------------------------------------------------------------------------------------------------
FUNCTION CL_LineCollide (L1 AS CL_LINE, L2 AS CL_LINE)
'--------------------------------------------------------------------------------------------------------
'- Tests for 2 lines intersecting (in collision) -
'- Returns: 0 if no collision, -1 if collision -
'- : CL_Instersect.Line.x, CL_Intersect.Line.y will contain the x,y coordinate of collision -
'- -
'- This function created from examples given at this discussion: -
'- https://forum.unity.com/threads/line-intersection.17384/ -
'- Based on the "Faster Line Segment Intersection" code found in Graphics Gems III -
'- pages 199-202 by Franklin Antonio (1992). ISBN 0-12-409672-7 -
'--------------------------------------------------------------------------------------------------------
SHARED CL_Intersect AS CL_INTERSECT ' intersection of collision
DIM Ax AS INTEGER
DIM Bx AS INTEGER
DIM Cx AS INTEGER
DIM Ay AS INTEGER
DIM By AS INTEGER
DIM Cy AS INTEGER
DIM d AS INTEGER
DIM e AS INTEGER
DIM f AS INTEGER
DIM x1lo AS INTEGER
DIM x1hi AS INTEGER
DIM y1lo AS INTEGER
DIM y1hi AS INTEGER
CL_Intersect.Line.x = 0 ' assume no collision
CL_Intersect.Line.y = 0
CL_LineCollide = 0
Ax = L1.Finish.x - L1.Start.x ' X bounding box test
Bx = L2.Start.x - L2.Finish.x
IF Ax < 0 THEN
x1lo = L1.Finish.x
x1hi = L1.Start.x
ELSE
x1hi = L1.Finish.x
x1lo = L1.Start.x
END IF
IF Bx > 0 THEN
IF x1hi < L2.Finish.x OR L2.Start.x < x1lo THEN EXIT FUNCTION
ELSE
IF x1hi < L2.Start.x OR L2.Finish.x < x1lo THEN EXIT FUNCTION
END IF
Ay = L1.Finish.y - L1.Start.y ' Y bounding box test
By = L2.Start.y - L2.Finish.y
IF Ay < 0 THEN
y1lo = L1.Finish.y
y1hi = L1.Start.y
ELSE
y1hi = L1.Finish.y
y1lo = L1.Start.y
END IF
IF By > 0 THEN
IF y1hi < L2.Finish.y OR L2.Start.y < y1lo THEN EXIT FUNCTION
ELSE
IF y1hi < L2.Start.y OR L2.Finish.y < y1lo THEN EXIT FUNCTION
END IF
Cx = L1.Start.x - L2.Start.x
Cy = L1.Start.y - L2.Start.y
d = By * Cx - Bx * Cy ' alpha numerator
f = Ay * Bx - Ax * By ' both denominators
IF f = 0 THEN EXIT FUNCTION ' parallel line check
IF f > 0 THEN ' alpha tests
IF d < 0 OR d > f THEN EXIT FUNCTION
ELSE
IF d > 0 OR d < f THEN EXIT FUNCTION
END IF
e = Ax * Cy - Ay * Cx ' beta numerator
IF f > 0 THEN ' beta tests
IF e < 0 OR e > f THEN EXIT FUNCTION
ELSE
IF e > 0 OR e < f THEN EXIT FUNCTION
END IF
CL_Intersect.Line.x = L1.Start.x + d * Ax / f ' calculate intersect coordinate
CL_Intersect.Line.y = L1.Start.y + d * Ay / f
CL_LineCollide = -1 ' report that collision occurred
END FUNCTION
'------------------------------------------------------------------------------------------------------------
FUNCTION CL_RectCollide (R1 AS CL_RECTANGLE, R2 AS CL_RECTANGLE)
'--------------------------------------------------------------------------------------------------------
'- Checks for the collision between two rectangular areas. -
'- Returns: 0 if no collision, -1 if collision -
'- : CL_Intersect.Rectangle.TopLeft.x, CL_Intersect.Rectangle.TopLeft.y, -
'- : CL_Intersect.Rectangle.BottomRight.x, CL_Intersect.Rectangle.BottomRight.y will contain the -
'- : x,y coordinates of the collision overlapping area -
'- : CL_Intersect.Rectangle.Width, CL_Intersect.Rectangle.Height will contain the width and -
'- : height of the collision overlapping area -
'--------------------------------------------------------------------------------------------------------
SHARED CL_Intersect AS CL_INTERSECT ' intersection of collision
R1.BottomRight.x = R1.TopLeft.x + R1.Width - 1
R1.BottomRight.y = R1.TopLeft.y + R1.Height - 1
R2.BottomRight.x = R2.TopLeft.x + R2.Width - 1
R2.BottomRight.y = R2.TopLeft.y + R2.Height - 1
RectCollide = 0
IF R1.BottomRight.x >= R2.TopLeft.x THEN ' check for overlapping coordinates
IF R1.TopLeft.x <= R2.BottomRight.x THEN
IF R1.BottomRight.y >= R2.TopLeft.y THEN
IF R1.TopLeft.y <= R2.BottomRight.y THEN ' all checks passed, must be a collision
' calculate collision area and return as a rectangle
IF R1.TopLeft.x > R2.TopLeft.x THEN
CL_Intersect.Rectangle.TopLeft.x = R1.TopLeft.x
ELSE
CL_Intersect.Rectangle.TopLeft.x = R2.TopLeft.x
END IF
IF R1.TopLeft.y > R2.TopLeft.y THEN
CL_Intersect.Rectangle.TopLeft.y = R1.TopLeft.y
ELSE
CL_Intersect.Rectangle.TopLeft.y = R2.TopLeft.y
END IF
IF R1.BottomRight.x < R2.BottomRight.x THEN
CL_Intersect.Rectangle.BottomRight.x = R1.BottomRight.x
ELSE
CL_Intersect.Rectangle.BottomRight.x = R2.BottomRight.x
END IF
IF R1.BottomRight.y < R2.BottomRight.y THEN
CL_Intersect.Rectangle.BottomRight.y = R1.BottomRight.y
ELSE
CL_Intersect.Rectangle.BottomRight.y = R2.BottomRight.y
END IF
CL_Intersect.Rectangle.Width = _
CL_Intersect.Rectangle.BottomRight.x - CL_Intersect.Rectangle.TopLeft.x
CL_Intersect.Rectangle.Height = _
CL_Intersect.Rectangle.BottomRight.y - CL_Intersect.Rectangle.TopLeft.y
CL_RectCollide = -1
END IF
END IF
END IF
END IF
END FUNCTION
'------------------------------------------------------------------------------------------------------------
FUNCTION CL_CircCollide (C1 AS CL_CIRCLE, C2 AS CL_CIRCLE)
'--------------------------------------------------------------------------------------------------------
'- Checks for the collision between two circular areas. -
'- Returns: 0 if no collision, -1 if collision -
'- : CL_Intersect.Circle.x, CL_Intersect.Circle.y will contain the x,y coordinate of the -
'- : collision -
'- Function optimized with removal of SQR() by Brandon Ritchie -
'--------------------------------------------------------------------------------------------------------
SHARED CL_Intersect AS CL_INTERSECT ' intersection of collision
DIM d AS INTEGER ' distance between circle centers
CL_CircCollide = 0
CL_Intersect.Circle.x = 0
CL_Intersect.Circle.y = 0
IF C1.Center.x + C1.Radius + C2.Radius > C2.Center.x THEN
IF C1.Center.x < C2.Center.x + C1.Radius + C2.Radius THEN
IF C1.Center.y + C1.Radius + C2.Radius > C2.Center.y THEN
IF C1.Center.y < C2.Center.y + C1.Radius + C2.Radius THEN
d = (C1.Center.x - C2.Center.x) * (C1.Center.x - C2.Center.x) + _
(C1.Center.y - C2.Center.y) * (C1.Center.y - C2.Center.y)
IF d <= (C1.Radius + C2.Radius) * (C1.Radius + C2.Radius) THEN
CL_Intersect.Circle.x = ((C1.Center.x * C2.Radius) + (C2.Center.x * _
C1.Radius)) / (C1.Radius + C2.Radius)
CL_Intersect.Circle.y = ((C1.Center.y * C2.Radius) + (C2.Center.y * _
C1.Radius)) / (C1.Radius + C2.Radius)
CL_CircCollide = -1
END IF
END IF
END IF
END IF
END IF
END FUNCTION
'------------------------------------------------------------------------------------------------------------
SUB CL_MakeMask (Obj AS CL_SPRITE)
'--------------------------------------------------------------------------------------------------------
'- Creates a negative mask of image for pixel collision detection. -
'- -
'- Obj - object containing an image and mask image holder -
'- -
'- This subroutine for internal library use only -
'--------------------------------------------------------------------------------------------------------
DIM x AS INTEGER ' image column and row counters
DIM y AS INTEGER
DIM cc AS _UNSIGNED LONG ' clear transparent color
DIM Osource AS LONG ' original source image
DIM Odest AS LONG ' original destination image
Obj.Mask = _NEWIMAGE(_WIDTH(Obj.Image), _HEIGHT(Obj.Image), 32) ' create mask image
Osource = _SOURCE ' save source image
Odest = _DEST ' save destination image
_SOURCE Obj.Image ' make object image the source
_DEST Obj.Mask ' make object mask image the destination
cc = _RGB32(255, 0, 255) ' set the color to be used as transparent
FOR y = 0 TO _HEIGHT(Obj.Image) - 1 ' cycle through image rows
FOR x = 0 TO _WIDTH(Obj.Image) - 1 ' cycle through image columns
IF POINT(x, y) = cc THEN ' is image pixel the transparent color?
PSET (x, y), _RGB32(0, 0, 0, 255) ' yes, set mask image to solid black
ELSE ' no, pixel is part of actual image
PSET (x, y), cc ' set mask image to transparent color
END IF
NEXT x
NEXT y
_DEST Odest ' restore original destination image
_SOURCE Osource ' restore original source image
_SETALPHA 0, cc, Obj.Image ' set image transparent color
_SETALPHA 0, cc, Obj.Mask ' set mask transparent color
END SUB
'------------------------------------------------------------------------------------------------------------
FUNCTION CL_PixelCollide (Obj1 AS CL_SPRITE, Obj2 AS CL_SPRITE)
'--------------------------------------------------------------------------------------------------------
'- Checks for pixel perfect collision between two rectangular areas. -
'- Returns: 0 if no collision, -1 if in collision -
'- : CL_Intersect.Pixel.x, cl_intersect.Pixel.y will contain the x,y coordinate of the collision -
'--------------------------------------------------------------------------------------------------------
SHARED CL_Intersect AS CL_INTERSECT ' intersection of collision
DIM x1 AS INTEGER ' upper left x,y coordinate of rectangular collision area
DIM y1 AS INTEGER
DIM x2 AS INTEGER ' lower right x,y coordinate of rectangular collision area
DIM y2 AS INTEGER
DIM Test AS LONG ' overlap image to test for collision
DIM Hit AS INTEGER ' -1 (TRUE) if a collision occurs, 0 (FALSE) otherwise
DIM Osource AS LONG ' original source image handle
DIM p AS _UNSIGNED LONG ' pixel color being tested in overlap image
IF Obj1.Mask = 0 THEN CL_MakeMask Obj1 ' create image masks if not present
IF Obj2.Mask = 0 THEN CL_MakeMask Obj2
Obj1.BottomRight.x = Obj1.TopLeft.x + _WIDTH(Obj1.Image) - 1 ' calculate lower right x,y coordinates
Obj1.BottomRight.y = Obj1.TopLeft.y + _HEIGHT(Obj1.Image) - 1 ' of both objects
Obj2.BottomRight.x = Obj2.TopLeft.x + _WIDTH(Obj2.Image) - 1
Obj2.BottomRight.y = Obj2.TopLeft.y + _HEIGHT(Obj2.Image) - 1
Hit = 0 ' assume no collision
CL_Intersect.Pixel.x = 0
CL_Intersect.Pixel.y = 0
'** perform rectangular collision check (proximity)
IF Obj1.BottomRight.x >= Obj2.TopLeft.x THEN ' rect 1 lower right X >= rect 2 upper left X ?
IF Obj1.TopLeft.x <= Obj2.BottomRight.x THEN ' rect 1 upper left X <= rect 2 lower right X ?
IF Obj1.BottomRight.y >= Obj2.TopLeft.y THEN ' rect 1 lower right Y >= rect 2 upper left Y ?
IF Obj1.TopLeft.y <= Obj2.BottomRight.y THEN ' rect 1 upper left Y <= rect 2 lower right Y ?
'** rectangular collision detected, perform pixel perfect collision check
IF Obj2.TopLeft.x <= Obj1.TopLeft.x THEN ' calculate overlapping
x1% = Obj1.TopLeft.x
ELSE
x1% = Obj2.TopLeft.x
END IF
IF Obj2.TopLeft.y <= Obj1.TopLeft.y THEN ' rectangle coordinates
y1 = Obj1.TopLeft.y
ELSE
y1 = Obj2.TopLeft.y
END IF
IF Obj2.BottomRight.x <= Obj1.BottomRight.x THEN
x2 = Obj2.BottomRight.x
ELSE
x2 = Obj1.BottomRight.x
END IF
IF Obj2.BottomRight.y <= Obj1.BottomRight.y THEN
y2 = Obj2.BottomRight.y
ELSE
y2 = Obj1.BottomRight.y
END IF
Test = _NEWIMAGE(x2 - x1 + 1, y2 - y1 + 1, 32) ' make overlap image
_PUTIMAGE (-(x1 - Obj1.TopLeft.x), -(y1 - Obj1.TopLeft.y)), Obj1.Image, Test ' image 1
_PUTIMAGE (-(x1 - Obj2.TopLeft.x), -(y1 - Obj2.TopLeft.y)), Obj2.Mask, Test ' image mask 2
'** enable the line below to see a visual representation of mask on image
'_PUTIMAGE (x1%, y1%), Test
x2 = x1
y2 = y1
y1 = 0 ' reset row counter
Osource = _SOURCE ' record current source image
_SOURCE Test ' make test image the source
DO ' begin row (y) loop
x1 = 0 ' reset column counter
DO ' begin column (x) loop
p = POINT(x1, y1) ' get color at current coordinate
'** if a color from object 1 found then a collision has occurred
IF p <> _RGB32(0, 0, 0, 255) AND p <> _RGB32(0, 0, 0, 0) THEN
Hit = -1
CL_Intersect.Pixel.x = x1 + x2 ' return collision coordinates
CL_Intersect.Pixel.y = y1 + y2
END IF
x1 = x1 + 1 ' increment to next column
LOOP UNTIL x1 = _WIDTH(Test) OR Hit ' leave when column checked or collision
y1 = y1 + 1 ' increment to next row
LOOP UNTIL y1 = _HEIGHT(Test) OR Hit ' leave when all rows checked or collision
_SOURCE Osource ' restore original destination
_FREEIMAGE Test ' test image no longer needed (free RAM)
END IF
END IF
END IF
END IF
CL_PixelCollide = Hit ' return result of collision check
END FUNCTION
Now a program is needed to make use of the collision library and test its features.
NOTE: You'll also need the files greenoval.PNG and redoval.PNG to execute this code. Both of these files are also included in your .\tutorial\Lesson20 directory.
( This code can be found at .\tutorial\Lesson20\CollisionLibraryDemo.bas )
' Collision Library V1.0 Demo
' Terry Ritchie 01/02/23
' quickbasic64@gmail.com
' See CollisionLibrary.DOC for documentation on the use of the collision library.
'
'$INCLUDE:'CollisionLibrary.BI'
CONST GREEN = _RGB32(0, 255, 0) ' color green
CONST RED = _RGB32(255, 0, 0) ' color red
CONST YELLOW = _RGB32(255, 255, 0) ' color yellow
DIM KeyPress AS STRING ' key user pressed
DIM Selection AS INTEGER ' selection user chose from menu (1 to 5)
DIM Greenbox AS CL_RECTANGLE ' green rectangle
DIM RedBox AS CL_RECTANGLE ' red rectangle
DIM GreenCircle AS CL_CIRCLE ' green circle
DIM RedCircle AS CL_CIRCLE ' red circle
DIM GreenLine AS CL_LINE ' green line
DIM RedLine AS CL_LINE ' red line
DIM GreenOval AS CL_SPRITE ' green oval image
DIM RedOval AS CL_SPRITE ' red oval image
DIM ObjectColor AS _UNSIGNED LONG ' color of colliding object
DIM Collision AS INTEGER ' true (-1) if objects in collision
DIM Plot(359) AS CL_POINT ' x,y points for rotating line
DIM c AS SINGLE ' 0 to 2*PI radian counter
DIM c1 AS INTEGER ' plot location
DIM c2 AS INTEGER ' plot location
'** Line collision demo setup **
FOR c1 = 0 TO 359 ' cycle through 360 radian points
Plot(c1).x = 319 + 100 * COS(c) ' calculate x,y point on a circle with 100 radius
Plot(c1).y = 239 + 100 * -SIN(c)
c = c + .0174533 ' increase 1/360th (2 * PI / 360)
NEXT c1
c1 = 0 ' starting x,y point of rotating line
c2 = 179 ' ending x,y point of rotating line
'** Rectangular collision demo setup **
Greenbox.TopLeft.x = 294 ' set variables for rectangular collision demo
Greenbox.TopLeft.y = 214
Greenbox.Width = 50
Greenbox.Height = 50
RedBox.Width = 25
RedBox.Height = 25
'** Circular collision demo setup **
GreenCircle.Center.x = 319 ' set variables for circular collision demo
GreenCircle.Center.y = 239
GreenCircle.Radius = 50
RedCircle.Radius = 25
'** Pixel perfect collision demo setup **
RedOval.Image = _LOADIMAGE("redoval.png", 32) ' set variables for pixel perfect collision demo
GreenOval.Image = _LOADIMAGE("greenoval.png", 32)
GreenOval.TopLeft.x = 294
GreenOval.TopLeft.y = 165
'** Begin main program **
SCREEN _NEWIMAGE(640, 480, 32)
_MOUSEHIDE
DO
CLS
LOCATE 2, 29: PRINT "COLLISION LIBRARY DEMO" ' display menu
LOCATE 4, 26: PRINT "Make Your Selection (1 to 5)"
LOCATE 7, 27: PRINT "1. Rectangle Collision"
LOCATE 9, 27: PRINT "2. Circle Collision"
LOCATE 11, 27: PRINT "3. Line Intersect Collision"
LOCATE 13, 27: PRINT "4. Pixel Perfect Collision"
LOCATE 15, 27: PRINT "5. Exit back to Windows"
DO ' wait for key press
_LIMIT 30
KeyPress = INKEY$
LOOP UNTIL KeyPress <> ""
Selection = VAL(KeyPress)
IF Selection > 0 AND Selection < 5 THEN ' continue if valid key press
DO
_LIMIT 30
CLS
LOCATE 28, 21: PRINT "Use mouse to move object, ESC to exit." ' print instructions
WHILE _MOUSEINPUT: WEND
SELECT CASE Selection
CASE 1 ' user chose rectangular collision demo
RedBox.TopLeft.x = _MOUSEX
RedBox.TopLeft.y = _MOUSEY
Collision = CL_RectCollide(Greenbox, RedBox)
LINE (Greenbox.TopLeft.x, Greenbox.TopLeft.y)-_
(Greenbox.BottomRight.x, Greenbox.BottomRight.y), ObjectColor, BF
LINE (RedBox.TopLeft.x, RedBox.TopLeft.y)-(RedBox.BottomRight.x, RedBox.BottomRight.y), RED, BF
IF Collision THEN
LINE (CL_Intersect.Rectangle.TopLeft.x, CL_Intersect.Rectangle.TopLeft.y)-_
(CL_Intersect.Rectangle.BottomRight.x, CL_Intersect.Rectangle.BottomRight.y), GREEN, BF
ELSE
ObjectColor = GREEN
END IF
CASE 2 ' user chose circular collision demo
RedCircle.Center.x = _MOUSEX
RedCircle.Center.y = _MOUSEY
CIRCLE (GreenCircle.Center.x, GreenCircle.Center.y), GreenCircle.Radius, ObjectColor
PAINT (GreenCircle.Center.x, GreenCircle.Center.y), ObjectColor, ObjectColor
CIRCLE (RedCircle.Center.x, RedCircle.Center.y), RedCircle.Radius, RED
PAINT (RedCircle.Center.x, RedCircle.Center.y), RED, RED
Collision = CL_CircCollide(RedCircle, GreenCircle)
IF Collision THEN
CIRCLE (CL_Intersect.Circle.x, CL_Intersect.Circle.y), 5, GREEN
PAINT (CL_Intersect.Circle.x, CL_Intersect.Circle.y), GREEN, GREEN
ELSE
ObjectColor = GREEN
END IF
CASE 3 ' user chose line intersection collision demo
LINE (GreenLine.Start.x, GreenLine.Start.y)-(GreenLine.Finish.x, GreenLine.Finish.y), ObjectColor
LINE (RedLine.Start.x, RedLine.Start.y)-(RedLine.Finish.x, RedLine.Finish.y), RED
RedLine.Start.x = _MOUSEX - 50
RedLine.Finish.x = RedLine.Start.x + 100
RedLine.Start.y = _MOUSEY
RedLine.Finish.y = RedLine.Start.y
GreenLine.Start = Plot(c1)
GreenLine.Finish = Plot(c2)
Collision = CL_LineCollide(RedLine, GreenLine)
IF Collision THEN
CIRCLE (CL_Intersect.Line.x, CL_Intersect.Line.y), 4, GREEN
PAINT (CL_Intersect.Line.x, CL_Intersect.Line.y), GREEN, GREEN
ELSE
ObjectColor = GREEN
END IF
c1 = c1 + 1
IF c1 = 360 THEN c1 = 0
c2 = c2 + 1
IF c2 = 360 THEN c2 = 0
CASE 4 ' user chose pixel perfect collision demo
_PUTIMAGE (GreenOval.TopLeft.x, GreenOval.TopLeft.y), GreenOval.Image
_PUTIMAGE (RedOval.TopLeft.x, RedOval.TopLeft.y), RedOval.Image
RedOval.TopLeft.x = _MOUSEX
RedOval.TopLeft.y = _MOUSEY
Collision = CL_PixelCollide(GreenOval, RedOval)
IF Collision THEN
CIRCLE (CL_Intersect.Pixel.x, CL_Intersect.Pixel.y), 4, YELLOW
PAINT (CL_Intersect.Pixel.x, CL_Intersect.Pixel.y), YELLOW, YELLOW
END IF
END SELECT
IF Collision THEN
LOCATE 2, 36: PRINT "COLLISION!" ' inform user of collision if occurring
ObjectColor = YELLOW
END IF
_DISPLAY
LOOP UNTIL _KEYDOWN(27) ' leave demo when ESC key pressed
_AUTODISPLAY
END IF
LOOP UNTIL Selection = 5 ' leave menu when 5 selected
_FREEIMAGE RedOval.Image ' RAM cleanup
_FREEIMAGE RedOval.Mask
_FREEIMAGE GreenOval.Image
_FREEIMAGE GreenOval.Mask
SYSTEM ' return to operating system
'$INCLUDE:'CollisionLibrary.BM'
Finally, no library is complete without documentation explaining how to utilize the library. For personal libraries I create my library documentation within the IDE using REM statements. This way I can simply copy the documentation directly into my main code body at the bottom for easy referral. When I'm finished writing my code I simply delete the documentation lines.
( This code can be found at .\tutorial\Lesson20\CollisionLibrary.DOC )
'****************** Collision Library V1.0
' * DOCUMENTATION * Terry Ritchie 01/02/23
'****************** quickbasic64@gmail.com
' CollisionLibrary.BI and CollisionLibrary.BM is a set of files to include in your source code that adds
' collision detection routines to your program. Open and execute the included CollisionLibraryDemo.BAS
' to see the concepts outlined in this documentation in action.
'
' Four collision detection routines are included in this library:
'
' ********************************************************
' * 1. Rectangle collision between two rectangular areas *
' ********************************************************
'
' collision% = CL_RectCollide(Rectangle1, Rectangle2)
'
' collision% will contain 0 (zero) if the two supplied rectangular areas are not in collision or -1
' if the areas are in collision (0 for false and -1 for true).
'
' The rectangular areas are defined using the CL_RECTANGLE type definition:
'
' DIM Rectangle1 AS CL_RECTANGLE ' rectangular areas
' DIM Rectangle2 AS CL_RECTANGLE
'
' The CL_RECTANGLE type definition contains the following subvariables that can be set:
'
' .TopLeft.x (integer) - the top left X coordinate of the rectangular area *
' .TopLeft.y (integer) - the top left Y coordinate pf the rectangular area *
' .BottomRight.x (integer) - the bottom right X coordinate of the rectangular area
' .BottomRight.y (integer) - the bottom right Y coordinate of the rectangular area
' .Width (integer) - the width of the rectangular area *
' .Height (integer) - the height of the rectangular area *
'
' * = these subvariables must be set for the CL_RectCollide() function to return useful results.
'
' RecTangle1.TopLeft.x,
' Rectangle1.TopLeft.y
' +-------------------------------------------------+
' | ^ |
' |<-------------|----- Rectangle1.Width ---------->|
' | | |
' | | |
' | Rectangle1.Height |
' | | |
' | | |
' | V |
' +-------------------------------------------------+
' Rectangle1.BottomRight.x,
' Rectangle1.BottomRight.y
'
' When the CL_RectCollide() function is called .BottomRight.x and .BottomRight.y are automatically
' calculated from the supplied .Topleft.x, .TopLeft.y, .Width, and .Height values supplied.
'
' If a collision is detected the variable CL_Intersect will contain the overlapping coordinates of the
' collision:
'
' CL_Intersect.Rectangle.TopLeft.x (integer) - the top left X coordinate of the overlapping area
' CL_Intersect.Rectangle.TopLeft.y (integer) - the top left Y coordinate of the overlapping area
' CL_Intersect.Rectangle.BottomRight.x (integer) - the bottom right X coordinate of the overlapping area
' CL_Intersect.Rectangle.BottomRight.y (integer) - the bottom right Y coordinate of the overlapping area
' CL_Intersect.Rectangle.Width (integer) - the width of the overlapping area
' CL_Intersect.Rectangle.Height (integer) - the height of the overlapping area
'
' +----------------------------------------------------------------------------+
' | Rectangle1 |
' | | * = overlapping area
' | CL_Intersect.Rectangle.TopLeft.x, |
' | CL_Intersect.Rectangle.TopLeft.y |
' | +*********************************************************+----------------------+
' | * ^ * |
' | *<------|---- CL_Intersect.Rectangle.Width -------------->* |
' | * | * |
' | * CL_Intersect.Rectangle.Height * |
' | * | * |
' | * V * |
' +------------------+*********************************************************+ |
' | CL_Intersect.Rectangle.BottomRight.x, |
' | CL_Intersect.Rectangle.BottomRight.y |
' | |
' | Rectangle2 |
' +--------------------------------------------------------------------------------+
'
' **************************************************
' * 2. Circle collision between two circular areas *
' **************************************************
'
' collision% = CL_CircCollide(Circle1, Circle2)
'
' collision% will contain 0 (zero) if the two supplied circular areas are not in collision or -1
' if the areas are in collision (0 for false and -1 for true).
'
' The circular areas are defined using the CL_CIRCLE type definition:
'
' DIM Circle1 as CL_CIRCLE ' circular areas
' DIM Circle2 as CL_CIRCLE
'
' The CL_CIRCLE type definition contains the following subvariables that can be set:
'
' .Center.x (integer) - the center X coordinate of the circular area
' .Center.y (integer) - the center Y coordinate of the circular area
' .Radius (integer) - the radius from the center x,y coordinate of the circular area
'
'
' Circle1.Radius
' __---|---__
' _-- | --_
' / | \
' | | |
' | V |
' | + |
' | Circle1.Center.x, |
' | Circle1.Center.y |
' \_ _/
' --__ __--
' -------
'
' If a collision is detected the variable CL_Intersect will contain the overlapping coordinate of the
' collision:
'
' .Circle.x (integer) - the X coordinate of the circle collision
' .Circle.y (integer) - the Y coordinate of the circle collision
'
' __-------__ __-------__
' _-- --_ _-- --_
' / \ / \
' | | | |
' | | |
' |CL_Intersect.Circle.x+CL_Intersect.Circle.y|
' | | |
' | | | |
' \_ _/ \_ _/
' --__ __-- --__ __--
' ------- -------
'
' ***********************************************
' * 3. Line collision between two line segments *
' ***********************************************
'
' collision% = CL_LineCollide(Line1, Line2)
'
' collision% will contain 0 (zero) if the two supplied line segments are not in collision or -1
' if the line segments are in collision (0 for false and -1 for true).
'
' The line segments are defined using the CL_LINE type definition:
'
' DIM Line1 AS CL_LINE ' line segments
' DIM Line2 AS CL_LINE
'
' The CL_LINE type definition contains the following subvariables that can be set:
'
' .Start.x (integer) - the x coordinate of the start of the line segment
' .Start.y (integer) - the y coordinate of the start of the line segment
' .Finish.x (integer) - the x coordinate of the end of the line segment
' .Finish.y (integer) - the y coordinate of the end of the line segment
'
' Line1.Start.x, Line1.Finish.x,
' Line1.Start.y Line1.Finish.y
' +--------------------------------+
'
' If a collision is detected the variable CL_Intersect will contain the line intersection coordinate of the
' collision:
'
' .Circle.x (integer) ' the x coordinate of line intersection
' .Circle.y (integer) ' the y coordinate of line intersection
'
' +
' * = intersection |
' |
' |
' +---------------*----------------+
' |CL_Intersect.Circle.x,
' |CL_Intersect.Circle.y
' |
' +
'
' ********************************************************
' * 4. Pixel perfect collision between two sprite images *
' ********************************************************
'
' collision% = CL_PixelCollide(Sprite1, Sprite2)
'
' collision% will contain 0 (zero) if the two supplied sprites are not in collision or -1
' if the sprites are in collision (0 for false, -1 for true).
'
' The sprites are defined using the CL_SPRITE type definition:
'
' DIM Sprite1 AS CL_SPRITE ' sprite images
' DIM Sprite2 AS CL_SPRITE
'
' The CL_SPRITE type definition contains the following subvariables that can be set:
'
' .Image (long integer) - the handle to an image file loaded with _LOADIMAGE() *
' .Mask (long integer) - the negative mask of the image (see below)
' .TopLeft.x (integer) - top left X coordinate of sprite image *
' .TopLeft.y (integer) - top left Y coordinate of sprite image *
' .BottomRight.x (integer) - bottom right X coordinate of sprite image
' .BottomRight.y (integer) - bottom right Y coordinate of sprite image
'
' * = these subvariables must be set for the CL_PixelCollide() function to return useful results.
'
' Sprite1.Image = _LOADIMAGE("Sprite1.png", 32)
' Sprite2.Image = _LOADIMAGE("Sprite2.png", 32)
'
' When the CL_SpriteCollide() function is called .BottomRight.x and .BottomRight.y are automatically
' calculated from the supplied .Topleft.x, .TopLeft.y, and the _WIDTH() and _HEIGHT() of the image.
' Also, the .Mask negative image is automatically created the first time CL_SpriteCollide() is called.
' You'll need to free this image manually along with the original image before your source code exits.
'
' _FREEIMAGE Sprite1.Image
' _FREEIMAGE Sprite1.Mask ' auto generated by CL_SpriteCollide()
' _FREEIMAGE Sprite2.Image
' _FREEIMAGE Sprite2.Mask ' auto generated by CL_SpriteCollide()
'
This library follows the .BI and .BM file requirements and all of the suggested rules when writing a set of library functions and procedures:
All variables and TYPE definitions in the .BI library file are prefaced with CL_ to identify them as belonging to the library. All functions and subroutines are also prefaced with CL_ in the .BM library file.
All TYPE definitions have been written to work seamlessly between functions and subroutines. Only one variable, CL_Intersect, has been created that is exposed to the main body code.
Documentation has been included to aid the programmer in the use of its features.
A sample program has been included to show the library in action.
A good example of the use of libraries can be found in the Minesweeper game I wrote. It utilizes three libraries that I wrote and brought together to create the game; A button library to create Windows style clickable buttons on screen, a menu library to create Windows style drop down menus, and graphics Line Input library to create interactive Line Input data fields.
Windows API Calls
QB64 also allows libraries within Windows (.DLL files) to be used as subroutines and functions. This functionality is beyond the scope of this tutorial but you need to be aware this exists. This is an advanced topic but opens up a whole new world of possibilities to the QB64 programmer. The following code uses a subroutine within the Windows kernel to report the physically installed amount of RAM within your computer.
NOTE: This code was written by Spriggsy and shared on the QB64 forum here.
( This code can be found at .\tutorial\Lesson20\PhysicalRAM.bas)
OPTION EXPLICIT
$NOPREFIX
$CONSOLE:ONLY
CONST KILOBYTE = 1024
CONST MEGABYTE = 1024 ^ 2
CONST GIGABYTE = 1024 ^ 3
CONST TERABYTE = 1024 ^ 4
DECLARE DYNAMIC LIBRARY "Kernel32"
SUB GetPhysicallyInstalledSystemMemory (BYVAL TotalMemoryInKilobytes AS OFFSET)
END DECLARE
DIM AS UNSIGNED INTEGER64 memory
GetPhysicallyInstalledSystemMemory OFFSET(memory)
memory = memory * KILOBYTE
SELECT CASE memory
CASE IS < KILOBYTE
PRINT USING " #### B"; memory
CASE IS < (MEGABYTE) AND memory >= KILOBYTE
PRINT USING "####.## KB"; (memory / KILOBYTE)
CASE IS < (GIGABYTE) AND memory >= (MEGABYTE)
PRINT USING "####.## MB"; (memory / (MEGABYTE))
CASE IS < (TERABYTE) AND memory >= (GIGABYTE)
PRINT USING "####.## GB"; (memory / (GIGABYTE))
END SELECT
If you wish to explore using Windows API calls the best place to start investigating and asking questions is the QB64 Phoenix Edition forum.