|====================================| | | | TELEMACHOS proudly presents : | | | | Part 1 of the PXD trainers - | | | | DOOM-WALLS | | the technique and tricks! | | | |====================================| ___---__--> The Peroxide Programming Tips <--__---___ <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><> Intoduction ----------- "Hi there! This is Telemachos of HASH. Now you might be wondering who the hell HASH is. Well - HASH is a great upcomming danish group. We make everything from PPE's, demos, utilities, GFX, ANSI, an e-mag, music and now also programming tuturials. So keep an eye out for future releases from us :)" Sigh! Well, some of you might have read the lines above before... YES this IS a re-release. But before nukin' this file from your HD please read the new information below. The truth is : HASH died... but Peroxide raised from the dust - featuring many new talented members. The main reason why HASH died is, that some of the members were more serious than others. Myself along with our founder Candyman, our musician Punqtured and our Net-coordinator Notion wanted the group to be a group know for its great releases - but rather the group became known as a bunch of ever-arguing, ever-flaming idiots constantly in war with another danish demogroup called The Renegades. So we decided to kill the group and together we formed Peroxide with the 4 before mentioned members as Seniors and a bunch of new (and some old) guys as group members. I really wanted to continue the trainer-serie I once started in HASH, and therefore I decided to to re-release this tut - now under another name : ___---__--> The Peroxide Programming Tips <--__---___ The goal for this serie has also changed a little. Instead of just writing about all the common topics in graphic programming (fx. 3d-rotations, plasma, fire, scrolling and so on) I'll try and stay on the path I originally set when starting a trainer-serie with a topic as complex as texturemapped 3d-worlds :) I'll try and write about the stuff that other tut-writers seems to have overlooked - the not not-so-common effects, the things that might sound a little boring compared to all the colorfull demo-effects :) I can't promise, though, that I won't release some info on all the common stuff also :) So drop me a letter telling me what to do :) If you got the original HASH-tut please replace it with this version.. a FEW minor bugs has been corrected in this version. Also please notice the new E-mail address. *************************** ATTENTION ARTISTS!!! ***************************** ARE YOU AN ARTIST ? DO YOU DRAW VGA-BITMAPS ? CAN YOU DO GFX IN ALL RESOLUTIONS - 320x200x256 AND VARIOUS SVGA-MODES ? DO YOU WANT TO SEE YOUR WORK IN AMAZING PRODUCTIONS ? CAN YOU DO GAME-GFX AS WELL AS BACKGROUNDS FOR DEMOS ? IF YOU MEET THE ABOVE TERMS (or some at least :) ), THEN DROP ME (TELEMACHOS) A MESSAGE OR MAIL THE GROUP AT : Peroxide@image.dk ****************************************************************************** In this trainer I will cover the techniques I used to draw 3d-walls similar to those in the famous computer game DOOM. I assume the reader have a basic understanding of how to program the VGA-card. Also I will be using quite a bit of math on high school level. If you don't understand vector-math you can still read this doc and end up beeing able to code some neat gfx, but then you won't understand what you're doing :=) I will drop code in Pascal, but it should be easy to convert into other languages. If you don't know how to program the VGA-card, you should read the trainers written by DENTHOR of ASPHYXIA. He has written (at this date) 21 great tuturials covering the basic VGA coding as well as quite a few nice demo effects. Search the I-net for DENTHOR and you will find the trainers somewhere. The 10 first trainers is also available through the PCGPE 1.0 If you want to get in contact with me, there are several ways of doing it : 1) Write me via FIDO-net : 2:235/350.22 2) E-mail me : tm@image.dk 3) Snail mail me : Kasper Fauerby Saloparken 226 8300 Odder Denmark 4) Call me (Voice ! ) : +45 86 54 07 60 Get this serie from the major demo-related FTP-sites or from our own homepage (soon at least ;) ) : http://www.image.dk/~peroxide or directly from my own : http://www.image.dk/~tm Well... enough talk - we wanna se some code ! ---------------------------------------------- Oki... code u want - code u get! First of all lets make some definitions.. My way of defining 3d-space is as follows : \ | \ | ----------------------------> X-axis | \ | \ | \ | \ | \ | \ | \> Z-axis (dissapears into the screen - in case u V don't understand my neat drawing :=) ) Y-axis Also I have another way of doing 3d -> 2d than most others. You have probably been told to use transformations like the following : X0 := 256*x div (z-Zoff) ( + Xoffset ) Y0 := 256*y div (z-Zoff) ( + Yoffset ) Well... I say - FORGET ALL ABOUT THE CRAP ABOVE!!!! The ONLY way to do correct 3d-transformations is to use the following formulas: X0 := Xofs + Round(X*(Zeye/(Zeye-Z))); Y0 := Yofs + Round(Y*(Zeye/(Zeye-Z))); I know, I know... It takes a little more calculations to use these formulas, but it is worth it - believe me! With the above formulas 3 new constants has been introduced : Xofs, Yofs and Zeye. The three constants together makes the coordinate-set of THE EYE through wich the 3d-world is viewed. I personnally likes to use the following values : Xofs = 160, Yofs = 100 and Zeye = -200 With these values the eye is placed in the middle of the screen (MCGA mode) some distance from the monitor. Yes that's correct - in your head :) The Xofs and Yofs defines the ORIGIN of your 3d-system - so the point (-5,4,0) will be at screen coord : (-5 + Xofs, 4 + Yofs), in my case (155,104) That done we can move on to the REAL subject of this trainer - the DOOM walls. A little speculations - boring perhaps, but nessesary :) --------------------------------------------------------- Well.. lets take a look at the poly we wanna texture map. As it's a doom-wall we know quite a few things about it. 1) Its left and right sides are ALWAYS parallel with the Y-axis of the screen. Well.. what does that means... It means that we can draw the mapped poly as a row of vertical lines - each with a different height and starting Y-pos. Take a look at this, and you'll know what i mean : P1 |\ | \ | | \ P2 | | | | | | | | | | | | | | / P3 | / |/ ^ P4 one of the vertical lines. 2) Each of the vertical lines has a CONSTANT Z-value. Now this is VERY important. If you follow a line with a constant Z-value, the corresponding line in TEXTURE-SPACE - that is, the bitmap you want to map to the poly - will have a CONSTANT SLOPE. Furthermore, if the line with constant z-value is vertical - like the ones we look at - then the line in texture-space will also be vertical. What does that mean ? Well.. it means that each vertical line in the poly just needs to be mapped with a scaled line from the texture. Now we have a formula for mapping a vertical line of the poly... 1) Find the height of the line in the poly - lets call it LH 2) We know the height of the texture - lets say it's 128. 3) Now we calculate the STEP we need to increse the Ypos in texture-space with for each pixel in the poly-line. STEP = 128 / LH. 4) we'll define the following variables : YposScreen, XposScreen - the start coordinate of the line in the poly. YposTexture, XposTexture - The position in texture-space. 5) Now lets map the damn thing ! for i := 1 to LH do begin mem[$a000:YposScreen*320 + XposScreen] := texture[XposTexture, YposTexture]; inc(YposScreen); YposTexture := YposTexture + STEP; end; Voila - here you go.. a texture mapped vertical line. Hey - neat, I hear you cry. Now this is easy.. I'll just calculate a STEP for movement along the X-side of the texture also.... and then just map each vertical line in the poly with a scaled line from the texture with the corresponding X-position in texture-space! Well.. try it! You will soon find out that result of this method is not to good! The poly IS mapped and the vertical lines looks beautiful... The problem is that the poly does'nt look 3d after all! It just looks like the texture has been squeezed a little on one side and then slammed to the screen. It surely does look more like a scaling than a texture mapping. But what's wrong then ??? -------------------------- WHY, I hear you cry! Now I thought it was so easy, and all I end up with is crap! Well.. dry your eyes - we'll find a way. The problem with the above mentioned method is that the human eye sees things in another way. When you look at something - a little from the side - notice that if you projected the object you look at to a 2d-screen, then the middle of the "texture" pasted to it WOULD NOT appear at the middle of the polygon !!!! Come on... Take a piece of paper and draw a line exactly down the middle of it. Then hold the paper at an arms length and look at it from the side. Does the line appear exactly at the middle of the projection you see of the paper ? NO... depending on the angle you are viewing the paper at, you will see the line move from the middle of the paper towards you. So what we need to do is to find a way of determing the correct X-INDEX in texture-space for each screen-column occupied by the poly. If you don't understand this, read the above paragraph again until you do! This is the very key to texture-mapping. Well.. we'll have to look at the wall a little more in 3d than we did before. Until now we have only looked at the PROJECTION of the wall.. now is the time to think 3d :=) And then again - we really don't have to! We just have to twist the view a little. Until now we have looked at the X,Y plane... Now lets take a look at the X,Z plane - ie. we look at the 3d-world from ABOVE the monitor : Z-axis ^ | | P1 a ray | \ / | \ / | B\ | / \ |(0,0,0)/ P2 > A wall seen from above. |/ / ----SL---------------O-----A----------SR----> X-axis ^ (-160,0,0) | / (50,0,0) ^ (160,0,0) | / | / | / E ^ eye (0,0,-200) Well.. neat ehh? If we look at the walls from above we can just look at them as LINES in a 2d space! Now, the point SL defines the left side of our computer monitor and SR defines the right side of our monitor. If we then makes one point along the X-axis equal 1 pixel along the screen then all we need to do when we want to know the x-index in the wall corresponding to a screen-column, is to calculate the intersection between the line made by the wall and the line drawn from the eye, through the screen- column and finaly through the wall-line. This is called ray-casting. As an example look at the ray drawn above. We know that the point A lies on the poly we get from projecting the wall P1,P2 to the screen. To find the correct index in texture-space we draw a virtual line from the eye E, through the point A and then finds the intersection with the wall - namely the point B. If we look at the drawing it looks like B is about 60% down the wall... If the texture is 128 pixels wide, then we know the correct X-index would be : 128 * 60% = 76.8 ... Rounded to 77. Ie. the vertical line at point A should be mapped with line 77 in the texture - even though we can clearly see, that A would be one of the first lines in the poly ! Here you go... do this with all of the screen columns occupied by the poly, and you have done a perspectively correct texture-maping! Now for the really ugly part... the vector math :=) ---------------------------------------------------- But in real life we can't just say : Humm... it looks like the intersection between the wall and the ray through screen coloumn X is about xx % down the wall.. We need to find a way of CALCULATING the exact intersection. Now it's time to introduce our good friend : -=[ Mr. Vector ]=-. If we set up the parametric equation for the ray and a parametric equation for the line representing the wall, all we have to do is to calculate the intersection between those two equations. Now, as long as we don't move our eye position during runtime we can precalculate all the parametric equations we'll ever need for our rays. All we have to do is to store the X and Z vector components in a small array. Since all the rays has an angle associated with it - namely the angle between the ray and the Z-axis - we can calculate a ray's vector components as COS and SIN of this angle. Here is how I did it : PROCEDURE Build_table; VAR counter : integer; angle : real; BEGIN for counter:=0 to 319 do BEGIN angle:=ARCTAN((counter-Xofs)/ABS(Zeye)*((2*pi)/360)); raytable[counter,1]:=Cos((2*pi)/360)*angle); raytable[counter,2]:=Sin((2*pi)/360)*angle); END; END; Now the array RAYTABLE contains the vector components of the ray through each screen column. I stored the components so Raytable[X,1] was the Z-comp and Raytable[X,2] was the Xcomp. So the parametric equation for a ray is : RayX = s * (X component of the ray) RayZ = s * (Z component of the ray) + Zeye Now all we need is the equation for the wall... Let the wall be difined by the points P1 and P2 - where P1 ALWAYS is the point farthest to the left. Then let P1X be the X-value for P1 and let P1Z be the Z-value. Guess what we'll call the X and Z value for P2 :=) To make the wall a vector subtract the left end values from the right end values. Ie. the parametric equation for the wall is : WallX = t * (P2X - P1X) + P1X WallZ = t * (P2Y - P1Y) + P1Y Now where does these two lines cross? They cross where WallX = RayX and WallZ = RayZ. So we get : s * (X component of the ray) = t * (P2X - P1X) + P1X s * (Z component of the ray) + Zeye = t * (P2Y - P1Y) + P1Y Now, because we used the hole wall to define the vector used in the line equation we know that if we solve the two equations above for t, then t will range from 0 to 1. It'll be the percent of the wall length where the two lines collide! To get the correct X-position in the texture multiply t by the textures width - in this case 128. Lets make our brains bleed, shall we ? We don't care about the variable s so lets isolate it in both equations : t *(P2X-P1X) + P1X 1) s = -------------------- Xcomp t*(P2Z-P1Z) + P1Z - Zeye 2) s = -------------------------- Zcomp well ... combine the two and get : t * (P2X-P1X) + P1X t * (P2Z-P1Z) + P1Z - Zeye -------------------- = ---------------------------- Xcomp Zcomp We don't like that divisor - lets get rid of it : t*Zcomp*(P2X-P1X) + P1X*Zcomp = t*Xcomp*(P2Z-P1Z) + P1Z*Xcomp - Zeye*Xcomp well ... lets get the t's on one side : t*(Zcomp*(P2X-P1X) - Xcomp*(P2Z-P1Z)) + P1X*Zcomp = P1Z*Xcomp - Zeye*Xcomp humm... lets isolate t : P1Z*Xcomp - Zeye*Xcomp - P1X*Zcomp t = ----------------------------------- Zcomp*(P2X-P1X) - Xcomp*(P2Z-P1Z) finally lets save a costly multiply : Xcomp * (P1Z-Zeye) - P1X*Zcomp t = ------------------------------------ Zcomp*(P2X-P1X) - Xcomp*(P2Z-P1Z) TADA... easy huh ? Now you got all you need to do texture mapping with correct perspective :) What ? You're to lazy to do the routines yourself ? ---------------------------------------------------- Well.. this section should be superfluous, but I'll do it anyway :) In this section I'll drop some pascal code to do texture mapping. I'll write it in plain pascal though, so you'll have to do the optimizations yourself - that is : use fixed point math, add clipping, convert it to assembler and find a better way of storing the texture data. As for the last point watch out for my next trainer, wich will cover the use of memory above the 640K base memory. Also you c(sh)ould add features as "see through" colors, to make your engine capeable of doing textures with windows, grates and more. All this is fortunately quite easy to do - trust me 8) Anyway here you go : VAR raytable : Array[0..319,1..2] of real; {each ray is Z-comp, X-comp} walltex : Array[0..128,0..128] of byte; FUNCTION index(X1,Z1,X2,Z2,C : integer) : real; BEGIN index:=(raytable[C,2]*(Z1-Zeye)-X1*raytable[C,1])/((X2-X1)* raytable[C,1]-(Z2-Z1)*raytable[C,2]); END; {given the line X1,Z1,X2,Z2 this function returns the parameter} {for the intersection of the line and the ray through coloumb C} PROCEDURE Loadtexture(Picture : string); VAR i,j : integer; BEGIN Display(picture,Vaddr,0,0); For i:=0 to 128 DO For j:=0 to 128 DO WallTex[j,i]:=GetPixel(j,i,Vaddr); Clear(0,Vaddr); END; PROCEDURE Texturemap(X1,Y1,X2,Y2,X3,Y3,X4,Y4,X1wall,Z1wall,X2wall,Z2wall :integer); VAR Xpos,Ypos,height : real; topslope,botslope : real; step : real; offset: word; width : integer; i,j : integer; BEGIN topslope:=(Y2-Y1)/(X2-X1); botslope:=(Y3-Y4)/(X3-X4); width:=ABS(X2-X1); height:=Y4-Y1+topslope-botslope; For i:=0 to width DO BEGIN Ypos:=0; Height:=height-topslope+botslope; step:=128/height; offset:=X1+i+(Round(topslope*i+Y1)*320); Xpos:=index(X1wall,Z1wall,X2wall,Z2wall,X1+i)*128; If Xpos<0 THEN Xpos:=0; {in case of small rounding-errors} If Xpos>128 THEN Xpos:=128; {we set Xpos to outer rim of texture} For j:=0 to Round(height) DO BEGIN Mem[$a000:offset]:=WallTex[Round(Xpos),Round(Ypos)]; Inc(offset,320); Ypos:=Ypos+step; END; END; END; Well... off with you ! Go make some stunning games and amaze the world :=) Last remarks.... ----------------- Well, that's about all for now. Hope you found this doc useful - and BTW : If you DO make anything public using these techniques please mention me in your greets or where ever you se fit. I DO love to see my name in a greeting :=) For a simplified demo of my current 3d-engine check out the archive demo.zip It contains a demo of my wall-drawing routines, but I have left out my monster routines 'cause they are still under development and the AI is not too great at this point either :) Humm.. the GFX is from the computer game MIGHT & MAGIC 4 and 5. I hope New World Computing won't sue me for using their great gfx in this tut :=) Even though my engine is running in stepmode these routines should be fast enough to make a fluid movement engine like DOOM. I use stepmode 'cause I like that kind of engines and I find them better when you're writing RPG's and not simple action games. As mentioned earlier, I'll do another trainer in near future about memory management. But if you wish me to continue doing trainers, you'll have to let me know someone's reading them. Also I would like to know if you find any errors in this text. No - NOT spelling errors! I live in Denmark, and in DK we speak DANISH! You're lucky to get this doc in english! Anyway - respond to this tut, or the serie will probably die! Keep on coding and CuL8'er M8's Telemachos - April '97