3DROTATE.TXT

─────────────────────────────────────────────────────────────────────────────
;
;     TITLE: 3d rotate text file
;WRITTEN BY: DRAEDEN
;      DATE: 02/22/93
;
;     NOTES: 3dRotate.asm requires a 386 or better
;
;ASSOCIATED FILES:
;
;       BWPRINT.ASM =>  Displays signed and unsigned bytes (8 bit), 
;                    >  words (16 bit), or double words(32 bit)
;
;       SINCOS.DW   =>  Contains data for the sine and cosine operations
;                    >  for a 256 degree circle. Values are multiplied by
;                    >  256 for fast calculations.
;
;       3DROTATE.ASM=>  The asm file.
;
;       MAKE.BAT    =>  The file that'll put it all together into an .EXE
;
────────────────────────────────────────────────────────────────────────────

Rotating a point around (0,0,0):

    Recall that the formula for rotating in 2d is:

    Xt = X*COS(φ) - Y*SIN(φ)
    Yt = X*SIN(φ) + Y*COS(φ)
    
    Rotations in the third deminsion simply involve rotating on all 3 planes.

    To rotate a point (X,Y,Z) around the point (0,0,0) you would use this
    algorithim:

    1st, rotate on the X axis
    
    Yt = Y*COS(Xan) - Z*SIN(Xan)
    Zt = Y*SIN(Xan) + Z*COS(Xan)
    Y = Yt              --  Note that you must not alter the cordinates
    Z = Zt              --   until both transforms are preformed
    
    Next, rotate on the Y axis

    Xt  = X*COS(Yan) - Z*SIN(Yan)
    Zt  = X*SIN(Yan) + Z*COS(Yan)
    X   = Xt
    Z   = Zt

    And finally, the Z axis

    Xt  = X*COS(Zan) - Y*SIN(Zan)
    Yt  = X*SIN(Zan) + Y*COS(Zan)
    X   = Xt
    Y   = Yt

    And thats it.  For a look at a quick implimentation, see the ASM file.
    
────────────────────────────────────────────────────────────────────────────

The Palette

    The palette consists of 256 color, each of which is made up of three
    bytes- red, green, & blue.  It is very important to note that each
    of the three bytes have a range of 0-63, because the VGA palette uses
    only 6 bit for each of the 3 colors (18 bits = 2^18 = 262,144 colors)

    The total size for a complete palette is 768 bytes (256*3)

WRITING the palette to the vga card can be done in one of two ways.  The
    first is to use VIDEO BIOS call which would go like this:

── method #1 ──    
    mov     ax,cs                   ;in this example, the palette data is 
    mov     es,ax                   ; stored in the code segment

    mov     dx,offset Palette       ;ES:DX points to palette data
    mov     ax,1012h                ;WRITE palette 
    mov     bx,0                    ;start at color 0                   
    mov     cx,256                  ;and write all 256 of 'em
    int     10h
───────────────

    The major disadvantage to this technique is that it is SLOW. VERY SLOW.
    But, it is easier to impliment.  While you should not use this
    in a rotating palette routine, it can be used to write the palette in
    one time situations, such as setting up a palette for displaying a 
    picture.

    The second way involves directly accessing the VGA card.  This is done
    as follows:

── method #2 ──
    mov     ax,cs                   ;the palette is in the code segment...
    mov     ds,ax

    mov     al,0                    ;the first color to write is # 0
    mov     dx,03c8h                ;VGA PEL address write mode register
    out     dx,al           
    inc     dx                      ;VGA PEL data register (03c9h)
    mov     si,offset PalTmp        ;point DS:SI to Palette
    mov     cx,256*3                ;the number of byte to write
    rep     outsb                   ;and go
───────────────

    Note that all palettes should be done right after a verticle retrace
    completes, so the 'snow' is avoided.  This is done like this:
───────────────
    mov     dx,3dah
VRT:
    in      al,dx
    test    al,8
    jnz     VRT         ;wait until Verticle Retrace starts
NoVRT:
    in      al,dx
    test    al,8
    jz      NoVRT       ;wait until Verticle Retrace Ends
───────────────

    The way I implimented the palette rotate in this program was not the 
    best way.  What I did is rotated the palette in host memory (make a
    copy of it exactly like I want it on the video card), and then write
    it to the video card.  The middle step is not needed.  I could have
    changed the code so that it is faster by doing the following:

    This rotates 16 colors. it starts the write a color # [index]
    be sure to never let [index] out of the range 0-15
    [BaseColor] is the color that is the first of the sixteen to 
    rotate.  
      
      Example: If you wanted to rotate color # 100-115 youd set
        [BaseColor] = 100  and 
        [Index]     = what ever the index is (0-15)

── methid #3 ──
    mov     bx,[index]              
    mov     ax,bx                   ;this bit of code is just a fast
    add     bx,bx                   ;way to multiple by three
    add     bx,ax                   ;using a few adds take onlt 6 clocks
                                    ;as compared to the 9-22 clocks for 
                                    ; IMUL BX,3
    mov     bp,16*3
    sub     bp,bx                   ;bx holds length of first half
                                    ;bp holds length of second half
    mov     dx,03c8h
    mov     al,[BaseColor]          ;the color # to start write at
    mov     al,[index]              ;start on teh second half first
    out     dx,al
    inc     dx
    mov     si,offset PalTmp        ;the place where the palette data is
    mov     cx,bp
    rep     outsb                   ;and go

    or      bx,bx
    je      SkipSecondHalf

    dec     dx
    mov     al,[BaseColor]          ;start at relative color # 0
    out     dx,al
    inc     dx                      ;si is already where we want it
    mov     cx,bx                   ;load in the length
    rep     outsb                   ;and go
SkipSecondHalf:
    ...                             ;code continues
───────────────

    Although this works great for cycling the palette, it is not effective 
    for fading out the palette.  To do this, you'd copy the current palette
    to a backup 768 byte buffer and then decrease every one of those 768
    bytes (but only if they are nonzero) and then write the entire backup
    palette using method #2.  This is very easy to impliment.

    But, if you were cycling the palette while fading, you'd want to
    do a hybrid of method #2 & #3. First you'd fade the palette, then you'd
    write all non-cycled areas and then write the rest of the palette using
    method #3.  If you get creative, you can make palette rotates very cool
    looking, and in fact its possible to do things that would be impossible
    without palette rotations.  Just take a look at our next demo whenever
    we get it done...

──────────────── 
And now the next part which I call - "The Nifty little cube that rotates 
around and leaves a trail of fading out dots"

    Nice title, eh?
    If you haven't guessed yet, the fading trick was done with (get ready)
    a cycling palette.

    Here's how I put it together:

        First, I made the routine that rotates the dots.  I took the previous
      file, ROTATE.ASM and added in the 3rd (and 4th) fields in the point
      structure.  Then I had to fiddle a little with the rotate subroutine
      to make it rotate on all three axis.  This was a little tricky. Compare
      the code of ROTATE.ASM and 3DROTATE.ASM to see what I did.  Next, I 
      had to come up with a little distance transform, so that the object 
      could zoom out into the distace, or get really close.  Here's how
      to do that:

──────────────── 
    ScreenX = ScreenDist*Xpos/Zpos
    ScreenY = ScreenDist*Ypos/Zpos
──────────────── 

      For convience, I chose the ScreenDist to be 256 (anyone know why?)
      I also put some additions into the calculation for easy transforms.
      The newer version of the above formula is:

──────────────── 
    ScreenX = 256*(Xpos+PreXadd)/(Zpos+PreZadd) + PostXadd
    ScreenY = 256*(Ypos+PreYadd)/(Zpos+PreZadd) + PostYadd
──────────────── 

      For normal display, all the Pre adds would be zero and
          PostXadd = ScreenWidth/2 
          PostYadd = ScreenHeight/2
      The post adds are for centering the object on the screen, and the
      preadds are for changing the 3d cordinates of the object.

      Finally, I implimented the formula.  Unfortunately, I could not
      avoid 2 divides. ( IDIV takes about 27 clocks )

──────────────── 
    mov     cx,[RotCord.Z +si]  
    add     cx,[PreAddZ]        ;cx = Zpos + PreZadd
    
    mov     ax,[RotCord.Y +si]
    add     ax,[PreAddY]        ;ax = Ypos + PreYadd

    movsx   dx,ah           ;these two instructions effectivly do a
    shl     ax,8            ;signed multiply by 256, using 6 clocks instead
                            ;of the 9-22 that IMUL takes

    idiv    cx              ;you are witnessing the evils of depth emulation-
    add     ax,[PostAddY]   ; the divide that you just can't get rid of
    mov     di,ax
    cmp     di,200          ;see if we are out of the screen bounds, if
                            ;we are, quit calculating for this point now.
    jae     DontDraw
    imul    di,320          ;this IMUL should be replace by a look-up table,
                            ; since we do the same thing over and over..
                            ; you'd do it like this:
                            ;
                            ; add di,di
                            ; mov di,cs:[Imul320+di]
                            ;
                            ; where Imul320 has 200 entries for the index*320
                            ; Easy enough, and it takes only 6 cycles
    mov     ax,[RotCord.X +si]
    add     ax,[PreAddX]

    movsx   dx,ah           ;again the multiply
    shl     ax,8

    idiv    cx              ;Aaarrrgghh! Another one!
    add     ax,[PostAddX]

    cmp     ax,320          ;check range of xpos
    jae     DontDraw
    add     di,ax           ;di now points to where the dot should be drawn
──────────────── 

        Then, I noticed that I was keeping a list of previous 'OldDI' points
      so that I could erase the old dots quickly.  I thought, "hmmm..
      what would happen if I were to create multiple OldDi charts?"   
        What would happen is that a trail would be left behind..  But that
      would be kinda lame, wouldn't it?  So I decided to put a rotating
      palette in there, too.  I'd set it up so that the bytes I just drew
      would be the brightest ones and all the others would "fade" out.
        It's a little difficult to explain, so just go look at the code, OK?

────────────────────────────────────────────────────────────────────────────

And now an explaination of SINCOS.DW

    SinCos.dw is a file which contians the sine of the 'angles' 0-255.  I
used 256 angles because it is very convienent, and there just happens to 
be a data structure that has a range of 0-255.  It's called a BYTE, denoted
by 'DB'.
    The bit of code (in BASIC) that would generate this sort of chart is:

────────

    FOR i = 0 TO 255
        an = i*2*pi/256
        BYTE = INT( SIN( an )*256 +.5)
        >> Store BYTE in a file <<
    NEXT i

────────

    Modifying the basic rotation formula for our data file would yield:

    Xt = (X*COS(φ) - Y*SIN(φ)) / 256
    Yt = (X*SIN(φ) + Y*COS(φ)) / 256

    If you know your hexadecimal, you'd realise that dividing by 256 is 
simply a "SAR XXX,8", where XXX is what you're dividing by 256.

    I expanded this into assembler, that not only works, but is very fast. 
To see it, examine the RotateXY procedure.

────────────────────────────────────────────────────────────────────────────

BWPRINT.ASM contains three functions: PrintByte, PrintWord, and PrintBig. 

    They do this:

    PrintByte: decodes a byte (in AL) and displays it as 3 digits plus a
            an optional sign.  If the carry is clear, it prints it as an
            unsigned integer.  If the carry is set, it prints it signed.

    ────
        EXAMPLE:
            mov     al,-50
            stc
            call    PrintByte
    ────

    PrintWord: decodes and prints a WORD (in AX) in 5 digits.

    ────
        EXAMPLE:
            mov     ax,50000
            clc
            call    PrintWord
    ────
    
    PrintBig:  decodes and prints a DOUBLEWORD (in EAX) in 10 digits. 
                NOTE: PrintBig requires a 386 to use.
    ────
        EXAMPLE:
            mov     eax,-1234567890
            stc
            call    PrintBig
────────────────────────────────────────────────────────────────────────────

   Well, that's it for now.  See INFO.VLA for information on contacting us.

   I would like some suggestions on what to write code for.  What would you
   like to see done?  What code would you like to get your hands on?

   Keep it easy, though.  I don't want to have to spend hours writing a DOC
   for it.  Just in case you're curious, I spent nearly as long on this doc
   as I did on the asm file...