ÄÄÄÄÄÄ ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» ÄÄÄÄÄÄ ÄÄÄÄÄÄÄÄÄÄÄÄ º TERRAFORMER º ÄÄÄÄÄÄÄÄÄÄÄÄ ÄÄÄÄÄÄ º TRAINER º ÄÄÄÄÄÄ ÄÄÄ ÈÍÍÍÍÍ» SERIES ÉÍÍÍÍͼ ÄÄÄ ÄÄÄÄÄÄÄ ÈÍÍÍÍÍÍÍÍÍÍÍÍͼ ÄÄÄÄÄÄÄ ÚÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Volume 6 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÙ ÄÄÄÄÄÄ by Jazm / Terraformer ÄÄÄÄÄÄ *** FRACTAL PLASMAS *** ============================================================================= INTRODUCTION ------------ Howdy all! After seeing my BBS intro, TPA_FRAC, a few people have asked me how to create fractal plasmas, so while Kiwidog is coding our sound player for (hopefully) Assembly '95, I'll be taking over the reigns for this trainer. Originally, this trainer was going to be a joint effort by Codex Hammer (Craig Agule) and myself with CH covering realtime plasmas and me doing the precalculated fractal plasmas, but due to free time restrictions, I'll be flying solo on this one. As formentioned, this trainer will show you the basics of fractal plasma generation. The idea behind it is fairly simple if you know what "recursive" means and how to implement it. After that, it's just a simple exercise in getting pixels, averaging values, and putting pixels. Piece of cake, right? I didn't comment the code at all because there wouldn't be enough room for everything I'd have to say, so there is an explanation of the example code (TFTRAIN6.PAS) at the end of this file. I tried to leave as much assembly as possible out of the code so that it can be read easier...just in case you haven't read all of the trainers in the Terraformer series. :) ============================================================================= RECURSIVE?! ----------- ...yeah, recursive. That's the basic premise for the whole thing. Recursion (for our pruposes) pretty much means that you call a function or procedure within itself. That may seem kinda stupid, if not impossible, for someone who hasn't seen the power of recursion, but in fact, it is extremely powerful (example: quicksort algo). Here's a very basic example of a recursive procedure. procedure countdown(x : integer); begin write(x, ' '); if x > 0 then countdown(x-1); end; begin {--Main Code--} countdown(5); end. Here is what the output would look like: 5 4 3 2 1 0 Countdown is originally called in the main part of the code passing 5 to the procedure. The procedure receives the parameter into x and writes it to the screen, and because x (or 5 now) is greater that 0, countdown is called FROM INSIDE ITSELF, but this time passing x-1 (or 4) as the parameter. This process continues until x=0 and then it has nowhere to go, so it exits. If you don't understand this example, stare at it for a while and trace the numbers out until you get it. Okay, now that you know how the above example works, there is one thing that needs to be said about recursive routines...they must have a trap door! Before a procedure/function calls itself, there should be an IF statement, a REPEAT UNTIL loop, or a WHILE loop. The reason for this is that if you didn't test for some limit, the routine would continue to calls itself until the stack overflows. Well, now that you've seen a very pointless example, let's see how recursivity can be used practically. How bout a factorial (n!) program... function factorial(x : integer) : integer; begin if x = 0 then factorial := 1 { 0! = 1 } else factorial := x * factorial(x-1); { n! = n * (n-1)! } end; Once you understand the above example, you've pretty much got recursion locked and it's time to go on to easier and more graphically related stuff. ============================================================================= MAKING BOXES OUT OF BOXES ------------------------- Here we go, this is the meat of the trainer right here. To create a fractal plasma, you need to be able to read pixels, find a midpoint value between two points, and write a pixel...all recursively. Throw in a couple of random numbers to mix things up and you have yourself an interesting pattern known as a plasma. What we need is to fill an area, a box, with pixels that relate to their neighboring pixels. This is almost easier done than said. Let's start out with our box, or rather just the 4 pixels that limit our box. We'll start out by putting a pixel in each corner with a random color value. A ... B . . . . . . C ... D That was easy. Now we're going to need to set ourselves up for recursion. This is where getting, averaging, and putting pixels comes into play. Lets find the midpoints of the 4 lines around the box. A .a. B . . b c . . C .d. D We have the positions, now what colors do we fill them with? Well, since we want the colors to relate to each other, how bout averaging the pixels around it? For example, the color value of 'a' would be the color value of 'A' averaged with the color value of 'B'. Now we just need to add 1 more pixel before we can say that this box is 'filled'...the middle pixel. Do this the same way...find the midpoint from either the pixels above and below or the pixels to the left and right. The color value of the middle pixel will be the average of the color values of the 4 corner pixels together PLUS OR MINUS a small random value that will prevent the filling routine from creating an even fade. (*NOTE* Each pixel has a location value and a color value and BOTH will need to be averaged in different places so make sure you keep them straight.) You're screen now looks like this with a, b, c, d, and e being the new points you just calculated. A .a. B . . b eÿÿ c . . C .d. D The box that you originally passed to the procedure is now considered 'filled'. Here's where recursion comes into play. As you can see, one pass of this 'filling' procedure doesn't exactly cover the screen with pixles. But the same thing that made the points you just created can also fill the entire box...if utilized correctly. Notice that you sent the 4 corners of a box to the procedure and with those 4 points, 5 new points were generated. Well, by making those 5 new points, didn't we also create the corners for 4 new boxes? Now we have 4 boxes that can be 'filled': [A,a,b,e], [a,B,e,c], [b,e,C,d], and [e,c,d,D]. If we send the points of box [A,a,b,e] to the filling procedure recursively (from within itself), we end up with this. A f a B g j h b i eÿÿ c C d D We now have 'filled' that box with the new points f, g, h, i, and j, but wait, by doing that, we've created 4 MORE new boxes that need to be filled. So we send the corners of the upper left box to the procedure again. We continue doing this until our trapdoor statement tells us that we're done. But how DO we know when we're done? The answer's simple, we only want to fill a box when there is space between the corners, right? So all we need to do is write a simple IF statement to check whether or not there is space between any 2 adjacent corners. We've now filled in all of the top-left boxes that have been created...so now just send the corners of the top-right box, then the bottom-left box, and finally the bottom-right box. One more note about about the characteristics of a recursive routine must be noted here. When a routine (procedure or function) is called from within itself, it first pushes the values of the variables that it is using onto the stack and THEN does it thing. (*NOTE* If you don't know what the stack is, you have 2 options. 1) Look it up somewhere else. 2) Just take my word for it.) The reason for this it to preserve the values of the variables that were used it previous rescursive calls. There is one disadvantage of this though, the stack is only so big. If you keep calling a routine from within itself, it will continue to push variables onto the stack until it overflows. This has one effect on our code...the beginning box must not be too big. You can't just decide to fill a 256x256 box. Our routine will use a lot of variables. We have 4 variables passed to the routine (the 4 corner location values), we have 5 variables that will hold the location values of the new points we generate, and we have 2 more for miscellaneous purposes. So every time our routine is called recursively, about 11 variables are being pushed onto the stack. This definitely limits the size of our original box. Take a look at the code and the explanation below and it shouldn't be too hard to figure out. It is much more complex than the simple examples given above, but the basic concept of recursion is the same. Choosing box size IS important. You need to satisfy 2 conditions in order to avoid bugs. 1) You can't use up all of your memory with huge sized boxes because of the above reason. 2) You have to choose a box size that is 2 raised to some power (i.e. 16, 32, 64, etc.) + 1. This is because when you find the midpoints, you're dividing by 2 and you want it to come out evenly. The extra 1 added on is damage control. That way, there is a whole number exactly between the 2 points, but a trunc gets rid of the extra 1 after the first box fill (if you don't understand that, just take my word for it). ============================================================================= TRANSLATE THAT CODE PLEASE -------------------------- I decided to write this little section instead of commenting the code because, if I tried to put everything that needs to be said into the code, you'd be drowning in comments. Unless you have a very good memory, you may want to print out either this explanation or the code so you can make references back and forth. Procedures and Functions: videomode -- should be self explanatory waitforverticalretrace -- also, name says it all setpal -- you HAVE read Terraformer's other trainers, right? plas_pal -- makes a nifty little palette for the plasma putpixel -- duh! (written in VERY simple terms) getpixel -- duh! again (again, written in slowest fashion possible) min -- returns the smaller of 2 numbers fillbox -- our recusive procedure that does the dirty work Global Variables: chaos -- the random number that is added or subtracted from the color value of the middle point generated...play with it for interesting results The Main Procedure: Before we start into the fillbox() procedure, we need to put some pixels at the corners of our original box. That way we don't have to pass the color value of those pixels to the procedure every time...we can just read them from the screen. I've used absolute mem addresses for the location values instead of (x,y) coordinates to save some space. Now, the fun part... The Fillbox() Procedure: First, let's get all these variables straight. Variables 'tl', 'tr', 'bl', and 'br' are the LOCATION values passed to the procedure...they stand for top-left, top-right, bottom-left, and bottom-right respectively. Variables 'top', 'left', 'right', 'bottom', and 'mid' are the LOCATION values of the 5 pixels that are generated within the procedure. Variables 'tlc', trc', 'blc', 'brc', 'tc', 'bc', 'lc', 'rc', and 'mc' are the COLOR values of the 9 pixels involved. Variables 'temp' and 'sf' will be explained later. Now the code... - The first statement is our trapdoor statement that checks if the corners are touching. - The next 5 lines generate the location values of the 5 new pixels. The formula used finds the midpoint of the 2 points written in parenthesis. Remember, (x shr 1) is equivalent to (x div 2). - The next 4 lines get the color values of the 4 corner pixels that we passsed to the procedure. - We now come across 4 sets of 4 lines each that begin with: "temp := getpixel...". There are 4 sets of instructions that look similar and basically do the same thing, but each for a different picture. I won't explain what these little blocks do any further than they get the color values the correspond to the location values of 'top', 'left', 'right', and 'bottom'. (Hey, you havta figure SOMETHING out on you own :> ). Just remember, the more randome numbers you throw in, the more interesting and diverse the plasma will tend to be. - The next block of instructions generate the color of the middle pixel. This is basically where we get all of the diversity. Because 'mc' is a byte, we can easily add a random number to it and cause it to loop back to zero (example: 255 + 4 = 3 when you're dealing with bytes). So we actually store the average of the color values of the other 4 new pixels plus a random number (controlled by the variable 'chaos' set in the main procedure) to a variable of type word. This way, we can see if the value is greater than 255 (the limit of a byte) and if it is, we'll try again until the random number allows a valid value (ie one that doesn't loop back to 0). - Now we put the 5 new pixels that we've just generated the location and color values of. - Finally, we call fillbox() recursively to 'fill' the 4 new boxes that we've created. ============================================================================= HUH??? ------ That's about it. This is the first trainer that I've written so I apologize to anyone that gets totally lost, because my explaing skillz may be a bit lacking. I know how to do this stuff and it's hard not to assume that someone reading this doesn't, but I tried. If you're really lost, you can contact me on The Programmer's Archive BBS (703-476-9015 email Jazm) or Greenway Data Connect BBS (formerly Data Connection 703-506-8598 email James Goodall) and I'll try my best to help you out. ============================================================================= I'M DONE NOW ------------ If you found this trainer helpful, great! We would appreciate it if you would greet Terraformer in you next production though. We will continue to release trainers such as this only if we see that it is being read. So let us know that you're out there. You can reach us via e-mail at any of the following addresses: kiwidog@mail.vt.edu -> Kiwidog. Any mail may be sent here. rpope@muselab.ac.runet.edu -> Primal Scream. Any business concerns should be directed here. mdavistr@mail.vt.edu -> Damnation Alley. Any questions regarding games or other non-technical questions can go here. Then of course there's always the tride-and-true SnailMail, which although it's slow we don't mind it at all. We will reply to all SnailMail but it may take some time: Chris Hargrove Terraformer Systems Inc. 1319 Grant St. Herndon, VA 22070 ============================================================================= MEMBERLIST OF TERRAFORMER ------------------------- Alias Real Name Position ------------------------------------------------------------------ Kiwidog Chris Hargrove Coder / PR / Organizer Codex Hammer Craig Agule Coder Marmot Steve Possehl Coder Necromancer Felix Tan Coder Jazm James Goodall Coder Damnation Alley Mitch Davister GFX Spawn Eddy Simmons GFX Lord Blanka the Black Mark Sanders Musician The Smeghead Dave Oranchak Musician Primal Scream Robert Pope Admin / PR / Musician If you would like Terraformer to work with you on a project such as a game or demo, feel free to contact us anytime using the above methods. Depending on our schedules we may or may not be able to contribute, but we'll be happy to see what we can do. ============================================================================= BYE --- See y'all at the next compo... Jazm / Terraformer June 3, 1995 11:44 PM =============================================================================