Dark Basic Matrix Primer
Note: Dark Basic Pro has additional matrix options that DB Classic does not have. In order to keep this tutorial compatible with both versions, these are not covered in this tutorial.
What Is A Matrix
Not to be confused with a mathematical matrix, a matrix in DB is the floor or terrain in your programs and although fairly simple to master, a couple of aspects are quite difficult for the novice to get to grips with. Apart from that, when you do know what you are doing, they are painfully long-winded to create manually, (as you will see later) - hence the popularity of matrix editors. Very few people type in all the Dark Basic commands to create a matrix - it would take days!
The actual matrix is a simple grid which you can think of as being similar to a chessboard. Each square can be painted with a texture and each of it's four corners raised or lowered to create hills and valleys.
Dark Basic gives you the ability to create your 'chessboard' with the number of squares (tiles) across and down that you like, as well as the physical width and depth that you want in pixels. A matrix in DB can have up to 10,000 polygons (triangles), so with each square tile consisting of two polygons, simple maths tells us that a matrix cannot have more than 5,000 tiles. This means you can create a matrix 1 tile deep and 5,000 tiles wide, or 10 deep and 500 wide, or 100 deep and 50 wide, or any combination up to 5,000 tiles maximum - you get the idea.
So, the biggest square matrix you can have in DB is 70x70 tiles which equals 4,900 tiles or 9,800 polygons - within our 10,000 limit. If the matrix was one tile bigger - 71x71, this would add up to 10,082 polygons and not be allowed - just in case you were wondering why DB has the strange number 70x70 as a maximum!
Most matrices (the plural of matrix, not matrixes), are produced square - mainly because the maths is easier. Plus, textures are also designed to look best on a square matrix tile, and a matrix 10 tiles wide and 5 tiles deep but having the same pixel width and height would have rectangular tiles - not square.
To correct this, you need to calculate the pixel width in relation to the height. With a square tile-sized matrix, no calculations are necessary as the pixel width and height is always the same.
If you want bigger landscapes than 70x70 tiles, you can create more than one matrix in your program and join them together, though if you have too many on screen at the same time, DB will start to slow down on lower spec machines. There are clever ways around this which we'll look at later.
The matrix is created with the command: MAKE MATRIX Mn,Pw,Pd,Tx,Tz where Mn is the matrix number, Pw & Pd is the pixel width and depth and Tx & Tz are the tiles across and down. When you create a matrix you tend to think of it most as being viewed from above in 2 dimensions - as shown in figure 1. The matrix is actually 3 dimensional so the X axis runs from left to right and it's actually the Z axis that runs from bottom to top - NOT the Y axis. Hence the variable Tz instead of Ty in the example above.
MAKE MATRIX 1,5000,5000,4,4
This will create the matrix in figure 1 and the bottom left corner is always placed at 0,0,0 in 3D space. You can of course move it, but it isn't advised until you are more experienced.
Looking at figure 1 again, you will see that there are two sets of numbers - white and purple. The larger white numbers are the X and Z values you use when referring to tiles when texturing. For this 4x4 tile matrix, the numbers along the X and Z axis run from 0 to 3.
In your DB code, your matrix tile width and height would be stored in variables such as TileWidth and TileHeight, so when texturing, you would use a loop like FOR N=0 TO TileWidth-1. That way, should the tiles across variable change, the loop will still texture all tiles.
The smaller purple numbers are used when altering the matrix height and as there are
two points along both axis for every matrix tile, there has to be an extra co-ordinate to handle this. As such, for a 4x4 tile matrix, height co-ordinates run from 0 to 4 and the corresponding DB loop would be something like FOR N=0 TO TileWidth (dropping the -1). When a matrix is first created each of these height values is set to zero. Positive values raise the point and negative values lower it.
Let There Be Height
Raising the height of any part of a matrix means altering the value of one of the tile intersect points, (corners). You cannot raise any other part of the matrix - the middle of a tile for example. Altering the height of one of the tiles means altering one or more of the four associated tile corner points. Setting all four points of a single tile to the same value will raise or lower the tile but keep it flat.
The DB command you use is SET MATRIX HEIGHT Mn, X, Z, H where Mn is the matrix number, X and Z are the intersection co-ords (the smaller purple numbers in fig 1) and H is the required height of that point. So, If we wanted to raise the tile highlighted with a red circle in fig 1, we need four commands - one for each of the four corner points:
You will see that the 'red blob' tile's bottom left corner is 2 across (X Axis) and 1 up (Z Axis), so the command for raising that corner to a height of 20.0 would be:
SET MATRIX HEIGHT 1, 2, 1, 20.0: Rem Bottom Left Tile Corner
The remaining three lines for the other three corner points would be:
SET MATRIX HEIGHT 1, 3, 1, 20.0: Rem Bottom Right
SET MATRIX HEIGHT 1, 3, 2, 20.0: Rem Top Right
SET MATRIX HEIGHT 1, 2, 2, 20.0: Rem Top Left
The height value should be a real (floating point) number, so you need to remember to put the .0 on the end. In practice integers do work, but you might as well get into the habit of doing it correctly now so that if at a later date a version of DB enforces these rules, you won't have to go back and alter all of your code.
So all that typing and all we have done is raised a single tile up a bit! Imagine the work involved in a 70x70 tile matrix! How would you decide what height values to use? By now you should be starting to realise the value of a matrix editor...
Another useful matrix height command to know about is RANDOMIZE MATRIX Mn, MaxHeight which will set the height values of ALL intersection points of a matrix with a single command. The height used is a random value between 0 and whatever you enter for the value MaxHeight.
IMPORTANT! After using any matrix command, it is important to remember to tell DB to refresh the matrix on the screen. This is done with UPDATE MATRIX Mn where Mn as usual is the matrix number. Failure to do this after altering anything on your matrix will result in no change on your screen!
A Splash Of Colour
All the work done so far will have been done on a wireframe matrix. To make the matrix a little more colourful and realistic, you need to texture the tiles you have created. This is done with SET MATRIX TILE Mn, X, Z, TextureNum and isn't helped by the confusing help files that come with DB that say "SET MATRIX TILE Matrix Number, X, Z, Tile Number".
To be able to use this command, it is very important that you have told DB to prepare your texture image ready for use with PREPARE MATRIX TEXTURE Mn, ImageVal, Across, Down - where ImageVal is the texture's image number and Across/Down is the texture grid size. So let's split all of that down into more easily followed sections:
A texture like the one above is a graphic image and can in theory be any size, but the smaller they are (and if they are square), then the faster DB will run.
Example sizes can be 32x32, 64x64, 128x128 or 256x256, (sizes in pixels). 512x512 can be used, but some graphics cards will start to struggle. Older Voodoo cards don't like anything over 256x256 or they throw a wobbler. The most commonly used size is 128x128 as the smaller the texture, the worse the quality. Having said that, if anyone remembers Equilibrium, that had an excellent quality matrix and that only used 32x32 textures! DB comes with a good selection of textures and there are thousands available on the web - including a small collection on this web site.
OK so far. Now for the rules which cause the problems...
First of all, each matrix can only have one single texture image associated with it. What!!?? Only one texture? No - only one texture IMAGE, though that image can have lots of individual textures in it. (see image below). This is done by placing them in a grid just like the old chessboard pattern again. This is when the texture size starts to get a little more important.
128x128 for individual textures is a happy medium between size and quality, but 16 textures of that size on a 4x4 grid in a texture image would be 512x512 pixels. Most new graphics cards would have the speed and memory to handle this size of texture image, but remember a little earlier I said that the Voodoo graphics cards are OK unless the texture image is greater than 256x256? You just have to bear in mind that nice, big, high quality textures can prevent an awful lot of users from using your programs!
OK, so what can you do? Easy - just reduce the size of your textures. A 256x256 texture image can contain 64 textures of 32x32 on an 8x8 grid and you'd be surprised how good they can look. Go up to textures 64x64 in size and you can get 16 textures on a 4x4 grid with double the quality. You can always go for the sod 'em attitude and not even worry about whether others can use your programs or not!
Next problem - making your texture. Your texture needs to be square, as too does the grid of textures in it. You need to know how many textures you need to fit into the image and make the grid big enough to fit them in - but keeping the recommended grid sizes of 2x2, 4x4, 8x8 or 16x16. It doesn't matter if some slots on the grid aren't used.
Note: These are recommended grid sizes, though I have also used others, like 3x3 and 6x6 and had no problems whatsoever. I think that the important thing is that they are square grids - ie NOT 3x4 or 4x5. Use what you like, but bear this in mind if you have texture glitches or problems.
For example, let's say you have 5 textures. A 2x2 grid will only hold 4 textures so you would use the next one up - 4x4 and only use the first 5 slots. The same size texture image could be used for up to 16 textures before having to move up to the next size. On this 4x4 grid image, each texture could be 64x64 and still fit in a 256x256 pixel image.
So, in summary, you need to decide on your texture size, which depends on the size you want your image to be and what grid size to use - which in turn depends on how many textures you use. Then, you have to create the image all using the correct sizes - either by hand in a paint program like Paintshop Pro, or via code in Dark Basic. If you add more textures as they are required, you have to edit the image - after possibly recalculating all the sizes!
All in all, it's not a straight forward process and once again it adds to the argument that you can't beat a matrix editor. For example, MatEdit allows you to have up to 100 textures in the texture palette and when you use the Build option, it does all the calculations and creates the texture image to the correct grid dimensions and texture sizes to fit - but only includes the textures from the palette that you actually used and not those you didn't!
OK, so let's say you now have your texture image with 4 seperate textures in a 2x2 grid. What next? Well, the texturing process is reasonably straight forward from here on. The textures are usually stored in a BMP file and we need to get them into an IMAGE, so we need to use LOAD IMAGE "ImageName", ImageNum. If we want to use the 2x2 example texture image above (and it was called texture.bmp) we would use:
LOAD IMAGE "texture.bmp", 1
Note the image number is 1 - it's needed in the next stage. Now we have the texture in an image we can use:
PREPARE MATRIX TEXTURE 1, 1, 2, 2
The first 1 is the matrix number, the second is the image number we loaded texture.bmp into. The last two numbers are the grid size X and Y for the textures in the image. Here we have 4 images on a 2x2 grid, so the values are 2 and 2. Basically, this command tells DB to prepare the image for the matrix by cutting it up into two images horizontally and two images vertically. The four resulting textures are numbered 1 to 4 and stored in memory ready for when you need them. Obviously, if your texture image was an 8x8 grid of textures, you would use the value 8 instead of 2.
Now the image has been prepared, we can now use DB's matrix commands to 'paint' the matrix.
If you want the whole matrix textured with the same texture, you can use FILL MATRIX Mn, Height, TileNum which will also set all the tiles to a given height at the same time with a single command. TileNum will be a number from 1 to x where x is the number of available textures in the texture image you loaded.
Note: Many new users don't realise that if you use PREPARE MATRIX TEXTURE with an image containing just one single texture, then FILL MATRIX is automatically called by DB and the whole matrix will be textured - without you asking for it! If like our example, you load a texture image which contains more than one texture, PREPARE MATRIX TEXTURE does nothing to the actual matrix.
If you want to texture individual tiles on the matrix, the process is very similar to the previously used method to alter the matrix heights - you just need to remember that there is one less co-ordinate to deal with.
Let's refer back to fig 1 for a moment below, but this time from a texturing perspective.
This time we are going to texture the tile with the blue blob using the water texture from our 2x2 texture image. We will do this using SET MATRIX TILE Mn, X, Z, TextureNum. Looking at fig 1, we can see that using the larger white numbers, that particular tile is at X=2, Z=2 and looking at the texture image, water is texture number 4. We know we are using matrix number 1, so this gives us:
SET MATRIX TILE 1, 2, 2, 4
Once again, you can see that creating a good looking terrain using this method would be a slow laborious process. However, there are ways to speed up the texturing process without a matrix editor. One method is to create your matrix grid on paper and list your textures - each one numbered. If you 'colour in' your matrix by putting the number of the texture you want in each grid square, you can transfer all the numbers to DATA statements then read all the texture values in a loop.
Let's assume a 5x5 matrix which has 6 used textures. Your sketch would look something like the one on the here.
Feeding the texture numbers into the matrix with a loop like FOR...NEXT usually counts from 0, so with this in mind, we need to build our data statement up reading from left to right working our way up the matrix starting at the bottom - remember, the bottom left corner tile is 0,0.
So, our data statement (placed at the end of the program) would look like this:
DATA 2,2,3,3,2,2,3,4,5,3,2,3,4,5,3,1,2,3,3,6,1,1,2,6,6
and the program code would look something like this:
TileWidth=5: Rem Matrix Tiles Width
TileHeight=5: Rem Matrix Tiles Height
FOR Nz=0 To TileHeight-1
FOR Nx=0 To TileWidth-1
Read TextureNum
SET MATRIX TILE 1, Nx, Nz, TextureNum
Next Nx
Next Nz
Update Matrix 1
Rem Data Statements At End Of Program
DATA 2,2,3,3,2,2,3,4,5,3,2,3,4,5,3,1,2,3,3,6,1,1,2,6,6
You could use the same loop method to make matrix height setting easier as well, but there's no easy way to know what values to use for each height like you can with colouring textures with pen and paper. If you are clever though, you can create an algorithm with SIN/COS to calculate the heights to create nice hills etc. Alternatively, you can do what many people do - write your own simple matrix editor if there isn't one out there that suits your purposes.
Clever Stuff
OK, it's been such a long tutorial, you may have forgotten that a little earlier I mentioned that there were clever ways to get around the 70x70 tile matrix limit. In the downloads section of my web site, there's a small demo where you can wander around a landscape which is 300x300 tiles square! I haven't tried it myself, but I'm informed by someone who has, that you can wander in the same direction for half an hour and still not hit the edge!
MatEdit's Monster Matrix option was restricted to 600x600 so that the whole terrain area would be visible on-screen in 800x600 screen mode. The fact is that the real maximum size of your terrain using the method I designed is limited only by the amount of memory you have. My main PC has 1.5 Gigabyte of memory and in theory I could create a terrain of such a size that it could take weeks - not hours - to walk from one side to the other!
So how is this done? Well, it's quite a simple idea really and a number of other DB users have improved on the idea since I first did it. So let's cover the theory first so you can go away and try to write something yourself...
The Theory
Normally, you control your character and he moves across the matrix landscape, the camera in tow. Another, slightly more difficult method is to place the character at 0,0,0 in space and reposition the matrix so that the character looks like it's standing in the centre of it. When the character is moved, it may animate and look like it is moving, but it doesn't - the matrix does!
In fact there is a command called SHIFT MATRIX which let's you scroll the matrix rows and/or columns of tiles up, down, left or right - without actually moving the matrix itself. Rows and columns which are scrolled 'off' the matrix re-appear automatically at the opposite edge of the matrix. Unfortunately, shifting the matrix by such huge amounts looks incredibly jerky and totally crap. So, what you do is physically move the matrix a small amount a number of times until it reaches the point that the next row or column of tiles is reached, at which point you put the matrix back at it's starting position and use the SHIFT MATRIX command.
If this is done carefully, it looks like the character is smoothly walking over the matrix. What's more, as the matrix is continuously scrolling with the SHIFT MATRIX command, if it has been designed correctly, you never reach the edge of the matrix whichever direction you travel. The result is a matrix which goes on forever - but it certainly isn't a huge matrix - the biggest it can be is 70x70 tiles and with the same landmarks encountered at regular intervals, the player will soon notice what is happening. There is an example of a never-ending matrix in the downloads section.
My idea was to create a 600x600 array with all the height data in it and another one with all the texture data in it. I didn't bother for MatEdit, (as it's only a matrix editor, not a world editor), but it would be a fairly simple process to create another array containing object position data.
On screen you have a single small matrix which actually needs to be no bigger than 10x10 tiles if fog is used effectively, though any size could be chosen. The same method of character control is used as described above by moving the matrix and then using SHIFT MATRIX, but the big difference being that just before the SHIFT MATRIX is used, the relevant tile row or column is completely replaced from the arrays. The routines keep track of the character's direction so it knows which rows/columns to update and the terrain doesn't repeat. Do the same thing with 100,000x100,000 arrays and you still only have a 10x10 tile matrix on screen, so there's no additional slowdown or lag when you are playing, just an amazingly large playing area!
There are many more tutorials and example code snippets on TGPF - my game programming forums. Click on the link below - it's free to join and everyone's welcome!
Visit TDKMan.com
TDK_Man