File 3DROTATE.ZIP from The Programmer’s Corner in
Category Assembly Language
3D rotate ASM routine.
File Name File Size Zip Size Zip Type
3DROTATE.ASM 22564 5849 deflated
3DROTATE.EXE 25856 2343 deflated
3DROTATE.MAP 232 132 deflated
3DROTATE.OBJ 6928 2714 deflated
3DROTATE.TXT 13416 4799 deflated
BWPRINT.ASM 2156 519 deflated
BWPRINT.OBJ 479 372 deflated
MAKE.BAT 486 251 deflated
SINCOS.DW 1974 523 deflated

## Contents of the 3DROTATE.TXT file

;
; 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
;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:

For normal display, all the Pre adds would be zero and
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]

mov ax,[RotCord.Y +si]

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:
;
; 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]

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

idiv cx ;Aaarrrgghh! Another one!

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...

December 6, 2017