home *** CD-ROM | disk | FTP | other *** search
/ The Unsorted BBS Collection / thegreatunsorted.tar / thegreatunsorted / programming / asm_programming / 3DROTATE.ZIP / 3DROTATE.TXT < prev    next >
Text File  |  1993-02-25  |  13KB  |  346 lines

  1. ─────────────────────────────────────────────────────────────────────────────
  2. ;
  3. ;     TITLE: 3d rotate text file
  4. ;WRITTEN BY: DRAEDEN
  5. ;      DATE: 02/22/93
  6. ;
  7. ;     NOTES: 3dRotate.asm requires a 386 or better
  8. ;
  9. ;ASSOCIATED FILES:
  10. ;
  11. ;       BWPRINT.ASM =>  Displays signed and unsigned bytes (8 bit), 
  12. ;                    >  words (16 bit), or double words(32 bit)
  13. ;
  14. ;       SINCOS.DW   =>  Contains data for the sine and cosine operations
  15. ;                    >  for a 256 degree circle. Values are multiplied by
  16. ;                    >  256 for fast calculations.
  17. ;
  18. ;       3DROTATE.ASM=>  The asm file.
  19. ;
  20. ;       MAKE.BAT    =>  The file that'll put it all together into an .EXE
  21. ;
  22. ────────────────────────────────────────────────────────────────────────────
  23.  
  24. Rotating a point around (0,0,0):
  25.  
  26.     Recall that the formula for rotating in 2d is:
  27.  
  28.     Xt = X*COS(φ) - Y*SIN(φ)
  29.     Yt = X*SIN(φ) + Y*COS(φ)
  30.     
  31.     Rotations in the third deminsion simply involve rotating on all 3 planes.
  32.  
  33.     To rotate a point (X,Y,Z) around the point (0,0,0) you would use this
  34.     algorithim:
  35.  
  36.     1st, rotate on the X axis
  37.     
  38.     Yt = Y*COS(Xan) - Z*SIN(Xan)
  39.     Zt = Y*SIN(Xan) + Z*COS(Xan)
  40.     Y = Yt              --  Note that you must not alter the cordinates
  41.     Z = Zt              --   until both transforms are preformed
  42.     
  43.     Next, rotate on the Y axis
  44.  
  45.     Xt  = X*COS(Yan) - Z*SIN(Yan)
  46.     Zt  = X*SIN(Yan) + Z*COS(Yan)
  47.     X   = Xt
  48.     Z   = Zt
  49.  
  50.     And finally, the Z axis
  51.  
  52.     Xt  = X*COS(Zan) - Y*SIN(Zan)
  53.     Yt  = X*SIN(Zan) + Y*COS(Zan)
  54.     X   = Xt
  55.     Y   = Yt
  56.  
  57.     And thats it.  For a look at a quick implimentation, see the ASM file.
  58.     
  59. ────────────────────────────────────────────────────────────────────────────
  60.  
  61. The Palette
  62.  
  63.     The palette consists of 256 color, each of which is made up of three
  64.     bytes- red, green, & blue.  It is very important to note that each
  65.     of the three bytes have a range of 0-63, because the VGA palette uses
  66.     only 6 bit for each of the 3 colors (18 bits = 2^18 = 262,144 colors)
  67.  
  68.     The total size for a complete palette is 768 bytes (256*3)
  69.  
  70. WRITING the palette to the vga card can be done in one of two ways.  The
  71.     first is to use VIDEO BIOS call which would go like this:
  72.  
  73. ── method #1 ──    
  74.     mov     ax,cs                   ;in this example, the palette data is 
  75.     mov     es,ax                   ; stored in the code segment
  76.  
  77.     mov     dx,offset Palette       ;ES:DX points to palette data
  78.     mov     ax,1012h                ;WRITE palette 
  79.     mov     bx,0                    ;start at color 0                   
  80.     mov     cx,256                  ;and write all 256 of 'em
  81.     int     10h
  82. ───────────────
  83.  
  84.     The major disadvantage to this technique is that it is SLOW. VERY SLOW.
  85.     But, it is easier to impliment.  While you should not use this
  86.     in a rotating palette routine, it can be used to write the palette in
  87.     one time situations, such as setting up a palette for displaying a 
  88.     picture.
  89.  
  90.     The second way involves directly accessing the VGA card.  This is done
  91.     as follows:
  92.  
  93. ── method #2 ──
  94.     mov     ax,cs                   ;the palette is in the code segment...
  95.     mov     ds,ax
  96.  
  97.     mov     al,0                    ;the first color to write is # 0
  98.     mov     dx,03c8h                ;VGA PEL address write mode register
  99.     out     dx,al           
  100.     inc     dx                      ;VGA PEL data register (03c9h)
  101.     mov     si,offset PalTmp        ;point DS:SI to Palette
  102.     mov     cx,256*3                ;the number of byte to write
  103.     rep     outsb                   ;and go
  104. ───────────────
  105.  
  106.     Note that all palettes should be done right after a verticle retrace
  107.     completes, so the 'snow' is avoided.  This is done like this:
  108. ───────────────
  109.     mov     dx,3dah
  110. VRT:
  111.     in      al,dx
  112.     test    al,8
  113.     jnz     VRT         ;wait until Verticle Retrace starts
  114. NoVRT:
  115.     in      al,dx
  116.     test    al,8
  117.     jz      NoVRT       ;wait until Verticle Retrace Ends
  118. ───────────────
  119.  
  120.     The way I implimented the palette rotate in this program was not the 
  121.     best way.  What I did is rotated the palette in host memory (make a
  122.     copy of it exactly like I want it on the video card), and then write
  123.     it to the video card.  The middle step is not needed.  I could have
  124.     changed the code so that it is faster by doing the following:
  125.  
  126.     This rotates 16 colors. it starts the write a color # [index]
  127.     be sure to never let [index] out of the range 0-15
  128.     [BaseColor] is the color that is the first of the sixteen to 
  129.     rotate.  
  130.       
  131.       Example: If you wanted to rotate color # 100-115 youd set
  132.         [BaseColor] = 100  and 
  133.         [Index]     = what ever the index is (0-15)
  134.  
  135. ── methid #3 ──
  136.     mov     bx,[index]              
  137.     mov     ax,bx                   ;this bit of code is just a fast
  138.     add     bx,bx                   ;way to multiple by three
  139.     add     bx,ax                   ;using a few adds take onlt 6 clocks
  140.                                     ;as compared to the 9-22 clocks for 
  141.                                     ; IMUL BX,3
  142.     mov     bp,16*3
  143.     sub     bp,bx                   ;bx holds length of first half
  144.                                     ;bp holds length of second half
  145.     mov     dx,03c8h
  146.     mov     al,[BaseColor]          ;the color # to start write at
  147.     mov     al,[index]              ;start on teh second half first
  148.     out     dx,al
  149.     inc     dx
  150.     mov     si,offset PalTmp        ;the place where the palette data is
  151.     mov     cx,bp
  152.     rep     outsb                   ;and go
  153.  
  154.     or      bx,bx
  155.     je      SkipSecondHalf
  156.  
  157.     dec     dx
  158.     mov     al,[BaseColor]          ;start at relative color # 0
  159.     out     dx,al
  160.     inc     dx                      ;si is already where we want it
  161.     mov     cx,bx                   ;load in the length
  162.     rep     outsb                   ;and go
  163. SkipSecondHalf:
  164.     ...                             ;code continues
  165. ───────────────
  166.  
  167.     Although this works great for cycling the palette, it is not effective 
  168.     for fading out the palette.  To do this, you'd copy the current palette
  169.     to a backup 768 byte buffer and then decrease every one of those 768
  170.     bytes (but only if they are nonzero) and then write the entire backup
  171.     palette using method #2.  This is very easy to impliment.
  172.  
  173.     But, if you were cycling the palette while fading, you'd want to
  174.     do a hybrid of method #2 & #3. First you'd fade the palette, then you'd
  175.     write all non-cycled areas and then write the rest of the palette using
  176.     method #3.  If you get creative, you can make palette rotates very cool
  177.     looking, and in fact its possible to do things that would be impossible
  178.     without palette rotations.  Just take a look at our next demo whenever
  179.     we get it done...
  180.  
  181. ──────────────── 
  182. And now the next part which I call - "The Nifty little cube that rotates 
  183. around and leaves a trail of fading out dots"
  184.  
  185.     Nice title, eh?
  186.     If you haven't guessed yet, the fading trick was done with (get ready)
  187.     a cycling palette.
  188.  
  189.     Here's how I put it together:
  190.  
  191.         First, I made the routine that rotates the dots.  I took the previous
  192.       file, ROTATE.ASM and added in the 3rd (and 4th) fields in the point
  193.       structure.  Then I had to fiddle a little with the rotate subroutine
  194.       to make it rotate on all three axis.  This was a little tricky. Compare
  195.       the code of ROTATE.ASM and 3DROTATE.ASM to see what I did.  Next, I 
  196.       had to come up with a little distance transform, so that the object 
  197.       could zoom out into the distace, or get really close.  Here's how
  198.       to do that:
  199.  
  200. ──────────────── 
  201.     ScreenX = ScreenDist*Xpos/Zpos
  202.     ScreenY = ScreenDist*Ypos/Zpos
  203. ──────────────── 
  204.  
  205.       For convience, I chose the ScreenDist to be 256 (anyone know why?)
  206.       I also put some additions into the calculation for easy transforms.
  207.       The newer version of the above formula is:
  208.  
  209. ──────────────── 
  210.     ScreenX = 256*(Xpos+PreXadd)/(Zpos+PreZadd) + PostXadd
  211.     ScreenY = 256*(Ypos+PreYadd)/(Zpos+PreZadd) + PostYadd
  212. ──────────────── 
  213.  
  214.       For normal display, all the Pre adds would be zero and
  215.           PostXadd = ScreenWidth/2 
  216.           PostYadd = ScreenHeight/2
  217.       The post adds are for centering the object on the screen, and the
  218.       preadds are for changing the 3d cordinates of the object.
  219.  
  220.       Finally, I implimented the formula.  Unfortunately, I could not
  221.       avoid 2 divides. ( IDIV takes about 27 clocks )
  222.  
  223. ──────────────── 
  224.     mov     cx,[RotCord.Z +si]  
  225.     add     cx,[PreAddZ]        ;cx = Zpos + PreZadd
  226.     
  227.     mov     ax,[RotCord.Y +si]
  228.     add     ax,[PreAddY]        ;ax = Ypos + PreYadd
  229.  
  230.     movsx   dx,ah           ;these two instructions effectivly do a
  231.     shl     ax,8            ;signed multiply by 256, using 6 clocks instead
  232.                             ;of the 9-22 that IMUL takes
  233.  
  234.     idiv    cx              ;you are witnessing the evils of depth emulation-
  235.     add     ax,[PostAddY]   ; the divide that you just can't get rid of
  236.     mov     di,ax
  237.     cmp     di,200          ;see if we are out of the screen bounds, if
  238.                             ;we are, quit calculating for this point now.
  239.     jae     DontDraw
  240.     imul    di,320          ;this IMUL should be replace by a look-up table,
  241.                             ; since we do the same thing over and over..
  242.                             ; you'd do it like this:
  243.                             ;
  244.                             ; add di,di
  245.                             ; mov di,cs:[Imul320+di]
  246.                             ;
  247.                             ; where Imul320 has 200 entries for the index*320
  248.                             ; Easy enough, and it takes only 6 cycles
  249.     mov     ax,[RotCord.X +si]
  250.     add     ax,[PreAddX]
  251.  
  252.     movsx   dx,ah           ;again the multiply
  253.     shl     ax,8
  254.  
  255.     idiv    cx              ;Aaarrrgghh! Another one!
  256.     add     ax,[PostAddX]
  257.  
  258.     cmp     ax,320          ;check range of xpos
  259.     jae     DontDraw
  260.     add     di,ax           ;di now points to where the dot should be drawn
  261. ──────────────── 
  262.  
  263.         Then, I noticed that I was keeping a list of previous 'OldDI' points
  264.       so that I could erase the old dots quickly.  I thought, "hmmm..
  265.       what would happen if I were to create multiple OldDi charts?"   
  266.         What would happen is that a trail would be left behind..  But that
  267.       would be kinda lame, wouldn't it?  So I decided to put a rotating
  268.       palette in there, too.  I'd set it up so that the bytes I just drew
  269.       would be the brightest ones and all the others would "fade" out.
  270.         It's a little difficult to explain, so just go look at the code, OK?
  271.  
  272. ────────────────────────────────────────────────────────────────────────────
  273.  
  274. And now an explaination of SINCOS.DW
  275.  
  276.     SinCos.dw is a file which contians the sine of the 'angles' 0-255.  I
  277. used 256 angles because it is very convienent, and there just happens to 
  278. be a data structure that has a range of 0-255.  It's called a BYTE, denoted
  279. by 'DB'.
  280.     The bit of code (in BASIC) that would generate this sort of chart is:
  281.  
  282. ────────
  283.  
  284.     FOR i = 0 TO 255
  285.         an = i*2*pi/256
  286.         BYTE = INT( SIN( an )*256 +.5)
  287.         >> Store BYTE in a file <<
  288.     NEXT i
  289.  
  290. ────────
  291.  
  292.     Modifying the basic rotation formula for our data file would yield:
  293.  
  294.     Xt = (X*COS(φ) - Y*SIN(φ)) / 256
  295.     Yt = (X*SIN(φ) + Y*COS(φ)) / 256
  296.  
  297.     If you know your hexadecimal, you'd realise that dividing by 256 is 
  298. simply a "SAR XXX,8", where XXX is what you're dividing by 256.
  299.  
  300.     I expanded this into assembler, that not only works, but is very fast. 
  301. To see it, examine the RotateXY procedure.
  302.  
  303. ────────────────────────────────────────────────────────────────────────────
  304.  
  305. BWPRINT.ASM contains three functions: PrintByte, PrintWord, and PrintBig. 
  306.  
  307.     They do this:
  308.  
  309.     PrintByte: decodes a byte (in AL) and displays it as 3 digits plus a
  310.             an optional sign.  If the carry is clear, it prints it as an
  311.             unsigned integer.  If the carry is set, it prints it signed.
  312.  
  313.     ────
  314.         EXAMPLE:
  315.             mov     al,-50
  316.             stc
  317.             call    PrintByte
  318.     ────
  319.  
  320.     PrintWord: decodes and prints a WORD (in AX) in 5 digits.
  321.  
  322.     ────
  323.         EXAMPLE:
  324.             mov     ax,50000
  325.             clc
  326.             call    PrintWord
  327.     ────
  328.     
  329.     PrintBig:  decodes and prints a DOUBLEWORD (in EAX) in 10 digits. 
  330.                 NOTE: PrintBig requires a 386 to use.
  331.     ────
  332.         EXAMPLE:
  333.             mov     eax,-1234567890
  334.             stc
  335.             call    PrintBig
  336. ────────────────────────────────────────────────────────────────────────────
  337.  
  338.    Well, that's it for now.  See INFO.VLA for information on contacting us.
  339.  
  340.    I would like some suggestions on what to write code for.  What would you
  341.    like to see done?  What code would you like to get your hands on?
  342.  
  343.    Keep it easy, though.  I don't want to have to spend hours writing a DOC
  344.    for it.  Just in case you're curious, I spent nearly as long on this doc
  345.    as I did on the asm file...
  346.