ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ; ; TITLE: 3d rotate ;WRITTEN BY: DRAEDEN /VLA CODER /iCE VGA CODER ; FOR: Phantasm, (206) 232-5912 ; The first programing oriented board to hit the 206 area. ; Any questions regarding this or ANY code in ANY programming ; language can, and will be answered on Phantasm. ; Send messages to 'Draeden' or post in the VLA programming ; section. ; ; The Deep (TDT/VLA), (305) 888-7824 ; Our first distribution site. Messages will also be answered ; if posted at this location. ; Send messages to 'The Kabal'. ; ; DATE: 02/25/93 ; ; NOTES: Compiled with TASM 2.51, TLINK 4.0 ; Must have a 386 or better to run, Moderate speed. ; This program was chosen as an example because it utilizes ; a lot of the neat little tricks you can do in assembly, ; mainly Structures (STRUC), Unions (UNION), INCLUDEs, ; the REPT macro, and the DUP() macro. It also introduces ; palette rotates in a less-than-boring application. ; ;ASSOCIATED FILES: ; ; BWPRINT.ASM => Displays signed and unsigned bytes, words, or ; > double words ; ; SINCOS.DW => Contains data for the sine and cosine operations ; ; 3DROTATE.TXT=> A text file that further explains palette rotates ; > and the basic 3d stuff. ; ; MAKE.BAT => The file that'll put it all together into an .EXE ; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ DOSSEG ;tells compiler to sort segments according to the ;DOS standards- code, data, stack .MODEL SMALL .STACK 200h ;sets up a 512 byte stack .DATA ;starts the data segment (empty) .CODE ;starts the code segment .386 ;tells compiler to allow 386 instructions ASSUME CS:@CODE, DS:@CODE ;tells compiler to assume offsets are taken from ;the code segment LOCALS ;turns local labels on eg. @@local: ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;=== GLOBALS -used to link multiple programs together GLOBAL PrintByte:PROC, PrintWord:PROC, PrintBig:PROC ;above is for the file BWPRINT.ASM ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;=== Data Includes -include physically puts the file in this one on compile INCLUDE sincos.dw ;Labels SINE: and COSINE: contains sine(0-255)*256 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;=== DATA Structures Angle_Union UNION B db 0 W dw 0 Angle_Union ENDS ;creates a new data type (eg. DW, DB, DD) called ;Angle_Union. Used just like in C Point_Struc STRUC X dw ? Y dw ? Z dw ? dw 0 ;a blank area to buffer it out to 8 bytes ; this is done so that access to each point ; is rapid; I can use a SHL XX,3 instead of ; a imul XX,6 saving a few cycles... Point_Struc ENDS ;Create a structure (or a record) ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;=== DATA INCXV EQU 3b00h ;scan code/ascii code for f1 DECXV EQU 3c00h ;f2 INCYV EQU 3d00h ;f3 DECYV EQU 3e00h ;f4 INCZV EQU 3f00h ;f5 DECZV EQU 4000h ;f6 INCDV EQU 4100h ;f7 DecDV EQU 4200h ;f8 StopRot EQU 3920h ;space bar ZeroAN EQU 1c0dh ;enter key IncPV1 EQU 0231h ;"1" DecPV1 EQU 0332h ;"2" IncPV2 EQU 0433h ;"3" DecPV2 EQU 0534h ;"4" MaxLag EQU 15 ;for now, leave at 15 MinDist EQU 200 MaxDist EQU 10000 Distance dw 300 DistanceVel dw 0 STPS EQU 10 ;size of each step NST EQU 100/STPS NumPts EQU NST*NST*NST/2 ;the number of points the program ; will rotate and display ;uses nested rept macros to build a solid cube XYZcord LABEL Point_Struc i=-25 REPT NST/2 j=-50 REPT NST k=-50 REPT NST Point_Struc k=k+STPS ENDM j=j+STPS ENDM i=i+STPS ENDM RotCord Point_Struc NumPts DUP(<>) ;holds rotated cordinates HeadDi dw 0 OldDi dw NumPts*MaxLag DUP (0) ;holds old di for quick erasing ;for MaxLag stored frames OffToCurDi dw offset OldDi ;points to the correct frame ;to erase and fill Zan Angle_Union Yan Angle_Union ; the '?' defaults to zero, but you can't Xan Angle_Union ;specify in a Union PathAn1 Angle_Union PathAn2 Angle_Union ZanVel db 1 ;angle velocities YanVel db 3 XanVel db -2 P1Vel db 1 P2Vel db 3 PreAddX dw 0 ;amount to ADD to each X, Y & Z >BEFORE< PreAddY dw 0 ;the distance transforms PreAddZ dw 0 ;causes the change to be scaled PostAddX dw 160 ;amount to ADD to each X & Y >AFTER< PostAddY dw 100 ;the distance transforms Palette db 3 dup (0) db 14 dup (60,40,30) db 63,0,0 db 2,0,0 db 7,0,0 db 9,0,0 db 12,0,0 db 15,0,0 db 17,0,0 db 20,0,0 db 22,0,0 db 25,0,0 db 30,0,0 db 35,0,0 db 40,0,0 db 45,0,0 db 50,0,0 db 63,0,0 PalTmp db 32*3 DUP(0) Color db 15 AngleMsg db "Ang: $" AngleVelMsg db "Vel: $" Control db "Control the angular velocity by hitting F1-F6 and 1 & 2",13,10 db "The distance is controlled by F7 & F8. Hit a key to start.$" Credits db 13,10,"Coded by Draeden of VLA",13,10,"$" ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;=== Code Includes ;none. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;=== SUBROUTINES ;DESTROYS: ax,dx,si,di,es,ds ;Input: BX= X CX= Y BP= Z ;OutPut:BX= X CX= Y BP= Z RotateXYZ proc near mov ax,cs mov ds,ax ; X-rotation ; Y := cos(Xan) * y - sin(Xan) * z ; Z := sin(Xan) * y + cos(Xan) * z mov si,[Xan.W] add si,si ; si = angle x mov ax,[Cosine+si] ; ax = cos(angle x) imul cx ; ax = cos(angle x) * y mov di,dx shl edi,16 mov di,ax ; store for later use mov ax,[Sine+si] ; ax = sin(angle x) imul bp ; ax = sin(angle x) * z shl edx,16 mov dx,ax sub edi,edx ; di = di-ax = cos(vx)*y - sin(vz)*z sar edi,8 ; remove the (co)sin "256-factor" mov es,di ; es = x-coordinate mov ax,[sine+si] ; ax = sin(angle x) imul cx ; ax = sin(angle x) * y mov di,dx shl edi,16 mov di,ax mov ax,[cosine+si] ; ax = cos(angle x) imul bp ; ax = cos(angle x) * z shl edx,16 mov dx,ax add edi,edx ; di = di-ax = sin(vx)*y + cos(vx)*z sar edi,8 ; remove the (co)sin "256-factor" mov cx,es ; update y mov bp,di ; update z ; Y-rotation ; X := cos(vy) * xc + sin(vy) * zc ; Z := -sin(vy) * xc + cos(vy) * zc mov si,[Yan.W] add si,si ; si = angle y mov ax,[Cosine+si] ; ax = cos(angle y) imul bx ; ax = cos(angle y) * x mov di,dx shl edi,16 mov di,ax ; store for later use mov ax,[Sine+si] ; ax = sin(angle y) imul bp ; ax = sin(angle y) * z shl edx,16 mov dx,ax add edi,edx ; di = di+ax = cos(vy)*x + sin(vy)*z sar edi,8 ; remove the (co)sin "256-factor" mov es,di ; es = x-coordinate mov ax,[Sine+si] ; ax = sin(angle y) neg ax ; ax =-sin(angle y) imul bx ; ax =-sin(angle y) * x mov di,dx shl edi,16 mov di,ax mov ax,[Cosine+si] ; ax = cos(angle y) imul bp ; ax = cos(angle y) * z shl edx,16 mov dx,ax add edi,edx ; di = di-ax = sin(vy)*x - cos(vy)*z sar edi,8 ; remove the (co)sin "256-factor" mov bx,es ; update x mov bp,di ; update z ; Z-rotation ; X := cos(vz) * xc - sin(vz) * yc ; Y := sin(vz) * xc + cos(vz) * yc mov si,[Zan.W] add si,si ; si = angle z mov ax,[Cosine+si] ; ax = cos(angle z) imul bx ; ax = cos(angle z) * x mov di,dx shl edi,16 mov di,ax mov ax,[Sine+si] ; ax = sin(angle z) imul cx ; ax = sin(angle z) * y shl edx,16 mov dx,ax sub edi,edx ; di = di-ax = cos(vz)*x - sin(vz)*y sar edi,8 ; remove the (co)sin "256-factor" mov es,di ; es = x-coordinate mov ax,[Sine+si] ; ax = sin(angle z) imul bx ; ax = sin(angle z) * x mov di,dx shl edi,16 mov di,ax mov ax,[Cosine+si] ; ax = cos(angle z) imul cx ; ax = cos(angle z) * y shl edx,16 mov dx,ax add edi,edx ; di = di+ax = sin(vz)*x+cos(vz)*y sar edi,8 ; remove the (co)sin "256-factor" mov bx,es ; update x mov cx,di ; update y ret RotateXYZ ENDP ;rotates all points and saves them RotateBox PROC NEAR pushad ;saves EVERYTHING (extended registers, too), except flags mov ax,cs mov ds,ax mov es,ax mov di,0 ;point counter @@DoNextPoint: ;Input: BX= X CX= Y BP= Z ;OutPut:BX= X CX= Y BP= Z mov bx,[XYZcord.X +di] ;load in cordinates to rotate mov cx,[XYZcord.Y +di] mov bp,[XYZcord.Z +di] push di call RotateXYZ pop di mov [RotCord.X +di],bx ;save rotated cordinates IN A DIFFERENT PLACE mov [RotCord.Y +di],cx add bp,[Distance] mov [RotCord.Z +di],bp add di,8 ;size of each entry cmp di,NumPts*8 ;are we done, yet? jb @@DoNextPoint ;No. Do another popad ret RotateBox ENDP ;draws the dots to the screen DrawBox PROC NEAR pusha ;saves only non extended registers mov ax,0a000h ;segment to VGA memory mov es,ax mov ax,cs mov ds,ax mov bp,0 ;point counter mov al,[Color] mov bx,[OffToCurDi] @@DoNextPoint: mov si,bp add si,si shl si,2 ;si= bp*8 mov ax,[HeadDi] add al,16 mov di,cs:[bx] cmp BYTE PTR es:[di],al ;makes sure that we only erase the dots we ;are sposed to.. works OK, BUT because we ;erase and draw to the same frame, a dot ;that we just drew in this loop could be ;erase, causing black spots in the object ;zoom it out too see what I'm saying jne NotMineDontErase mov BYTE PTR es:[di],0 ;clear out old point NotMineDontErase: ;pixel location = ScreenWidth*Ypos + Xpos = 320 * (Y+AddY) + X + AddX mov cx,[RotCord.Z +si] add cx,[PreAddZ] mov ax,[RotCord.Y +si] add ax,[PreAddY] movsx dx,ah shl ax,8 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 jae DontDraw imul di,320 mov ax,[RotCord.X +si] add ax,[PreAddX] movsx dx,ah shl ax,8 idiv cx ;Aaarrrgghh! Another one! add ax,[PostAddX] cmp ax,320 jae DontDraw add di,ax mov [bx],di mov ax,[HeadDi] add al,16 stosb RESUMEDRAW: add bx,2 inc bp cmp bp,NumPts ;are we done, yet? jb @@DoNextPoint ;No. Do another ;adjust head pointer call ChangePalette inc [HeadDi] cmp [HeadDi],MaxLag jb NotAtEnd mov [HeadDi],0 NotAtEnd: mov bx,[Headdi] add bx,bx imul bx,NumPts add bx,offset OldDi mov [OffToCurDi],bx popa ret DontDraw: mov byte ptr [bx],0 jmp short RESUMEDRAW DrawBox ENDP ChangePalette PROC NEAR pusha mov ax,cs mov ds,ax mov es,ax mov si,offset Palette+3*15 mov di,offset PalTmp mov bp,[HeadDi] mov cx,bp ;this bit of code is a quick way to add bp,bp ; add bp,cx ;multiply bp by 3 add di,bp ;sets up for copy #1 mov cx,16*3 sub cx,bp rep movsb ;copies block #1 or bp,bp je NoBlock2 mov di,offset PalTmp mov cx,bp rep movsb NoBlock2: mov al,16 ;we start the write at color #16 mov dx,03c8h out dx,al inc dx mov si,offset PalTmp mov cx,16*3 ;write 16 colors (3 bytes per color) rep outsb popa ret ChangePalette ENDP ;DESTROYS: flags and AX ;updates all the distances, angles, ect... AddAngles PROC NEAR mov ax,[DistanceVel] add [Distance],ax cmp [Distance],MinDist jge DistMinOk mov [DistanceVel],0 mov [Distance],MinDist DistMinOk: cmp [Distance],MaxDist jle DistMaxOk mov [DistanceVel],0 mov [Distance],MaxDist DistMaxOk: mov al,[P1Vel] add [PathAn1.b],al mov al,[P2Vel] add [PathAn2.b],al mov al,[ZanVel] add [Zan.b],al mov al,[YanVel] add [Yan.b],al mov al,[XanVel] add [Xan.b],al ;note that by just increasing the byte part, the ;ranging is automatic (stays in 0-255 range) mov bx,[PathAn1] ;this little section of code fixes up the add bx,bx ;path that the object travels mov ax,[Sine+bx] ;PathAn1 controls the X-Z PreAdds and mov cx,[Cosine+bx] ;PathAn2 controls PreYadd sar ax,3 ;Because the sine and cosine chart are sar cx,4 ;multiplied by 256, dividing by 8 and 16 mov [PreAddX],ax ;is the same as multiplying the (co)sine by mov [PreAddZ],cx ;32 and 16 respectivly ;this gives the object a slightly nauseating mov bx,[PathAn2] ;bobbing pattern add bx,bx mov ax,[Sine+bx] sar ax,4 mov [PreAddY],ax ret AddAngles ENDP ;DESTROYS: AX,BX,DX, FLAGS ;puts all the text up top DisplayStuff PROC NEAR mov ah,2 mov bx,0 mov dx,0 int 10h ;set cursor pos to (dl,dh) on page BX mov ah,9 mov dx,offset AngleMsg int 21h mov al,[Xan.B] clc ;says print it unsigned call PrintByte mov al,[Yan.B] clc ;says print it unsigned call PrintByte mov al,[Zan.B] clc ;says print it unsigned call PrintByte mov ax,[Distance] clc ;says print it unsigned call PrintWord mov al,[PathAn1.B] clc ;says print it unsigned call PrintByte mov al,[PathAn2.B] clc ;says print it unsigned call PrintByte mov ah,2 mov bx,0 mov dx,0100h int 10h ;set cursor pos to (dl,dh) on page BX mov ah,9 mov dx,offset AngleVelMsg int 21h mov al,[XanVel] stc ;says print it signed call PrintByte mov al,[YanVel] stc ;says print it signed call PrintByte mov al,[ZanVel] stc ;says print it signed call PrintByte mov ax,[DistanceVel] stc ;says print it signed call PrintWord mov al,[P1Vel.B] stc ;says print it signed call PrintByte mov al,[P2Vel.B] stc ;says print it signed call PrintByte ret DisplayStuff ENDP ;DESTROYS: a bunch of registers ;does all the keyboard oriented stuff.. ;clears carry if we are to continue, sets it if we are to quit DoKeyInput PROC NEAR mov ah,11h int 16h ;has a key been pressed? Z flag is set if not jnz GetTheKey clc ;we continue ret GetTheKey: mov ah,10h ;a key has been pressed, int 16h ; get it in AX (al= ascii, ah=scan code) cmp al,27 ;was it the ESCAPE key? jne KeepGoing stc ;we signal a quit ret KeepGoing: ;despite its apparent ungainly look, this cmp ax,INCXV ;is very fast..as little as 2 clocks for each je DoINCXV ;unsuccessful compare on the 486 and 5 clocks on cmp ax,DECXV ;the 386... A rather quick way to do it... je DoDECXV cmp ax,INCYV je DoINCYV cmp ax,DECYV je DoDECYV cmp ax,INCZV je DoINCZV cmp ax,DECZV je DoDECZV cmp ax,INCDV je DoIncDV cmp ax,DECDV je DoDecDV cmp ax,ZEROAN je DoZeroAn cmp ax,STOPROT je DoStopRot cmp ax,INCPV1 je DoIncPv1 cmp ax,DECPV1 je DoDecPv1 cmp ax,INCPV2 je DoIncPv2 cmp ax,DECPV2 je DoDecPv2 clc ;no valid keypress... Continue ret DoINCXV: inc [XanVel] clc ret DoINCYV: inc [YanVel] clc ret DoINCZV: inc [ZanVel] clc ret DoDECXV: dec [XanVel] clc ret DoDECYV: dec [YanVel] clc ret DoDECZV: dec [ZanVel] clc ret DoINCDV: inc [DistanceVel] clc ret DoDECDV: dec [DistanceVel] clc ret DoStopRot: mov [XanVel],0 mov [YanVel],0 mov [ZanVel],0 mov [DistanceVel],0 clc ret DoZeroAn: mov [Xan.W],0 mov [Yan.W],0 mov [Zan.W],0 clc ret DoINCPv1: inc [P1Vel] clc ret DoDECPv1: dec [P1Vel] clc ret DoINCPv2: inc [P2Vel] clc ret DoDECPv2: dec [P2Vel] clc ret DoKeyInput ENDP ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ ;=== CODE START: mov ax,cs mov ds,ax mov es,ax mov ah,9 ;print a dollar sign terminating string mov dx,offset control int 21h mov ah,0 int 16h ;wait for a keypress mov ax,0013h ;set 320x200x256 mode int 10h mov dx,offset Palette ;ES:DX points to palette data mov ax,1012h ; WRITE palette mov bx,0 ;start at color 0 mov cx,16 ; and write 16 of 'em int 10h MainLoop: call RotateBox call AddAngles 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 call DrawBox call DisplayStuff call DoKeyInput jnc MainLoop ;jump if carry is clear ByeBye: mov ax,0003h ;set 80x25x16 text int 10h push cs pop ds mov ah,9 mov dx,offset credits int 21h mov ax,4c00h ;return control to DOS int 21h END START