The easiest way to get three dimensional objects onto the screen is to use wireframe models. A wireframe model represents all surfaces of a 3-D object as an outlined object. This includes the opposite sides and all internal components that you usually cannot see. It's a less complex method for representing 3-D images. A wireframe model is useful because three dimensional lines appear as straight lines and not as curves with both parallel projections and the vanishing point perspective. Therefore, you can limit yourself to corner
point transformations and you will not need to push, rotate and illustrate every point to the edge. If you then combine these calculated corner points, you have a realistic picture of the figure as a wireframe model. The most important section of this model is the line drawing algorithms. The speed of display depends on these line algorithms since the transformations themselves require hardly any computational time. The fastest way to draw a line is with the Bresenham algorithm, which we'll use in this chapter.
You can find complete mathematical derivation of this algorithm in many books so we won't delve into its principles.
The algorithm is limited to angles between 0 and 45 degrees. As the line is being drawn, the algorithm decides whether each point should be placed exactly to the right of the previous point or directly above it. The decision as to which of the two points will be next is similar to the fixed decimal procedure discussed. A variable (Dist, stored in BP) is dependent on the last step (right or directly above), either raised to Add_1
(in SI) or to Add_2 (in DI) and dependent on whether Dist is positive or negative. The angle limitations can be removed; to handle angles between
45 and 90 degrees, you can switch x and y. Negative angles are handled by reversing this direction. The exact procedure is listed as follows:
.286
b equ byte ptr w equ word ptr data segment
extrn vpage:word ;current video page data ends
putpixel macro ;puts pixel at ax/bx pusha
xchg ax,bx ;exchange x and y push ax ;store y for later mov cx,bx ;get x
and cx,3 ;mask plane
mov ax,1 ;and set corresp. bit shl ax,cl
mov ah,2 ;TS register 2 xchg ah,al
mov dx,3c4h out dx,ax
pop cx ;get y
mov ax,80d ;calculate row offset mul cx
shr bx,2 ;add column offset add bx,ax
add bx,vpage ;write to current page mov b es:[bx],3 ;and set color
popa endm
code segment public assume cs:code,ds:data public bline
bline proc near
;draws line from ax/bx to cx/dx push bp
push ax ;store x0 and
PC
PCunderground
The following procedure is part of the BRES.ASM file on the companion CD-ROM
push bx ;y0
mov bx,4340h ;prepare self modification sub cx,ax ;calculate deltax
jns deltax_ok ;negative ?
neg cx ;yes, then reverse deltax sign
mov bl,48h ;and decrement ax instead of incrementing ax deltax_ok:
mov bp,sp ;addressing of y1 on the stack sub dx,ss:[bp] ;calculate deltay
jns deltay_ok ;negative ?
neg dx ;yes, then reverse deltay sign
mov bh,4bh ;and decrement bx instead of incrementing bx deltay_ok:
mov si,dx ;deltay and or si,cx ;deltax = 0 ? jne ok
add sp,6 ;then ax, bx and bp from stack and end ret
ok:
mov w cs:dist_pos,bx ;write dec/inc ax/bx to destination cmp cx,dx ;deltax >= deltay ?
jge deltax_great
xchg cx,dx ;no, then exchange deltax and deltay mov bl,90h ;and increment ax noppen
jmp constants deltax_great:
mov bh,90h ;otherwise increment bx noppen constants:
mov w cs:dist_neg,bx ;write dec/inc ax/bx to destination shl dx,1 ;define add_2
mov di,dx ;store in di sub dx,cx ;define start-dist mov bp,dx ;and store in bp mov si,bp ;define add_1 sub si,cx ;and store in si mov ax,0a000h ;load VGA segment mov es,ax
pop bx ;retrieve stored values for x0 and y0 pop ax
loop_p:
putpixel ;set pixel or bp,bp ;dist positive ? jns dist_pos
dist_neg:
inc ax ;increment x (if necessary, self modification) inc bx ;increment y (if necessary, self modification) add bp,di ;update dist
loop loop_p ;next pixel jmp finished ;finished dist_pos:
inc ax ;increment x (if necessary, self modification) inc bx ;increment y (if necessary, self modification) add bp,si ;update dist
loop loop_p ;next pixel finished: pop bp ret bline endp code ends end
This procedure draws a line in Mode X from the point at coordinates (ax/bx) to point (cx/dx). The current page (offset in vpage) is considered.
The step into the third dimension next takes us to unit VAR_3D.PAS, which contains the most important global variables. The meaning of those global variables will become apparent in the following sections:
Unit Var_3d; Interface Uses Tools;
Const Txt_Number=5; {number of used textures}
Txt_Size: {size specifications of textures} Array[0..Txt_Number-1] of Word=
($0a0a,$0a0a,$0a0a,$0a0a,$0a0a);
Var vz:Word; {shifting into the screen} rotx, {angle of rotation} roty,
rotz:word; {3 degree steps} sf_sort:Boolean; {sort surfaces ?}
Fill:Boolean; {true: Fill / false:Lines} sf_shift:Boolean; {suppress surface shift ?} Texture:Boolean; {use textures ?}
lightsrc:Boolean; {use light source ?} Glass:Boolean; {glass surface ?} Txt_Data:Array[0..Txt_Number-1] of Pointer;
{location of textures in memory} Txt_Offs:Array[0..Txt_Number-1] of Word;
{offset within the texture picture} Txt_Pic:Pointer; {pointer to texture picture}
Sine:Array[0..149] of Word; {sine table for rotations} Implementation
Begin
Sin_Gen(Sine,120,16384,0); Move(Sine[0],Sine[120],60); End.
This unit is used by all our 3-D programs. It initializes the sine tables used for the rotations. Subsequently, the first quarter (30 entries = 60 byte) of this table is appended to the end. This has the advantage that both its sine (zero in sine[0]) and its cosine values (zero in sine[30])
can be inferred.
The Bresenham algorithm is used in the 3D_WIRE.PAS program, which direct the assembly module to clear the global variable filling, to draw wireframe models:
Uses Crt,ModeXLib,var_3d; Const worldlen=8*3; {Point-Array} Worldconst:Array[0..worldlen-1] of Integer = (-200,-200,-200, -200,-200,200, -200,200,-200, -200,200,200, 200,-200,-200, PC PCunderground
You can find VAR_3D.PAS on the companion CD-ROM
PC
PCunderground
You can find 3D_WIRE.PAS on the companion CD-ROM
200,-200,200, 200,200,-200, 200,200,200); surfclen=38; {Surface-Array} surfcconst:Array[0..surfclen-1] of Word= (0,4, 0,2,6,4, 0,4, 0,1,3,2, 0,4, 4,6,7,5, 0,4, 1,5,7,3, 0,4, 2,3,7,6, 0,4, 0,4,5,1,0,0); Var i,j:Word;
procedure drawworld;external; {Draws the world on current video page} {$l 3dasm.obj}
{$l poly.obj} {$l bres.obj} {$l root.obj}
Begin
vz:=1000; {solid is located at 1000 units depth} vpage:=0; {start with page 0}
init_modex; {enable ModeX}
rotx:=0; {initial values for rotation} roty:=0;
rotz:=0;
Fill:=false; {SurfaceFill off} sf_sort:=false; {SurfaceSort off}
sf_shift:=false; {SurfaceShift suppression off} Glass:=false; {glass surfaces off}
repeat
clrx($0f); {clear screen} DrawWorld; {draw world}
switch; {switch to finished picture} WaitRetrace; {wait for next retrace} Inc(rotx); {continue rotating ... } If rotx=120 Then rotx:=0;
Inc(rotz);
If rotz=120 Then rotz:=0; inc(roty);
if roty=120 Then roty:=0;
Until KeyPressed; { ... until key} TextMode(3);
End.
You'll find the definitions for the world and the surfaces at the start of this program and in the following programs. The world includes only the coordinates for the corners, which are simply listed in the x, y, z series order for every given point. A relationship between these points is first established by defining the surface. The characteristics of each surface are established in this array.
The first word for every surface definition includes the top surface structure. We will talk more about top surface structure in later sections but it's simply listed here as 0. This is followed by the number of the corner points of this surface and the numbers of the corner points themselves, whereby their numbering starts with 0.
The first square consists of the corner points 0,2,6,4, which corresponds to the coordinates (-200/-200/-200), (-200,200,-200), (200,200,-200). (200,-200,-200). The definition of the top surface is complete by specifying two consecutive zero words, otherwise it will not be recognized by the procedure.
In the main program, the global depth vz of the object is then set (placed). Reduce this value to zoom closer. The variables rotx, roty and rotz indicate the object's rotation angle in steps of 3 degrees; i.e., a rotation of 15 degrees is established with a value of 5.
The choice of graphic mode is assigned to Mode X. This mode has a few disadvantages relating to the speed at which it can address individual pixel but its most important advantage is its two screen pages. This is important with the textures, because here the painting of the screen takes longer than a retrace period. The global directional variables are then set. In this instance, the surface (filling), the sorting (fl_sort), the hiding of the reverse side (fl_backs) and the glass top surfaces (glass) are turned off.
The loop that follows is the same for all the programs and follows this pattern if no key is pressed: 1. The screen is cleared every time.
2. The world is drawn.
3. It's switched over to the new screen page. 4. It's rotated further.
The main function of this program takes over the module 3DASM.ASM, which is written in assembly language for faster execution. Instead of listing the source code in individual sections, we've listed the entire source code at one time:
.286
w equ word ptr b equ byte ptr
surfclen equ 200 ;maximum length of surface defined Pointlen equ 4*100 ;length of point array
num_ar equ 30 ;maximum number of areas num_cor equ 10 ;maximum number of corners
data segment ;external variables from Pascal segment extrn vz:word ;total depth
extrn rotx:Word ;angle of rotation extrn roty:Word
extrn rotz:word
extrn worldconst:dataptr ;array with points
extrn surfcconst:dataptr ;array with surface definitions extrn lightsrc:word ;flag for light source shading extrn sf_sort:word ;flag for surface sorting
extrn sf_shift:word ;flag for surface shift suppression extrn Texture:Byte ;flag for textures
extrn Fill:Byte ;flag for fill / wireframe model crotx dw 0 ;x, y and z angle as offset to croty dw 0 ;specific sine value
crotz dw 0
rotx_x dw 0 ;x,y,z to x-rot rotx_y dw 0
rotx_z dw 0
roty_x dw 0 ;to y-rot roty_y dw 0
PCPCunderground
You can find 3DASM.ASM on the companion CD-ROM
roty_z dw 0
rotz_x dw 0 ;to z-rot, final rotz_y dw 0
rotz_z dw 0
startpoly dw 0 ;start of definition of current area Point dw Pointlen dup (0);receives calculated coordinates Pointptr dw 0 ;pointer in Point-Array
Point3d dw Pointlen dup (0) ;receives completed 3D-coordinates (texture) mean dw num_ar*2 dup (0) ;list of mean z-values
meanptr dw 0 ;pointer in Mean-Array n dw 0,0,0,0,0,0 ;normal vector 32 Bit n_amnt dw 0 ;amount of normal vector
extrn sine:dataptr data ends
extrn drawpol:near ;draws area as wireframe model extrn fillpol:near ;fills area
extrn root:near ;calculates root of ax
getdelta macro ;calculates the two surface vectors mov ax,poly3d[0] ;x: original corner
mov delta2[0],ax ;store temporarily in delta2 sub ax,poly3d[8] ;obtain difference to first point mov delta1[0],ax ;and delta1 finished
mov ax,poly3d[2] ;y: original corner
mov delta2[2],ax ;store temporarily in delta2 sub ax,poly3d[10d] ;obtain difference to first point mov delta1[2],ax ;and delta1 finished
mov ax,poly3d[4] ;z: original corner
mov delta2[4],ax ;store temporarily in delta2 sub ax,poly3d[12d] ;obtain difference to first point mov delta1[4],ax ;and delta1 finished
mov bp,polyn ;select last point dec bp
shl bp,3 ;8 bytes at a time mov ax,poly3d[bp] ;get x
sub delta2[0],ax ;obtain difference mov ax,poly3d[bp+2] ;get y
sub delta2[2],ax ;obtain difference mov ax,poly3d[bp+4] ;get z
sub delta2[4],ax ;obtain difference endm
setcoord macro source,offst ;sets calculated screencoord .386
mov ax,source ;project coordinate cwd
shld dx,ax,7 shl ax,7 idiv cx
add ax,offst ;middle of screen is 0/0/0 mov bx,Pointptr ;note in Point-Array mov Point[bx],ax
add Pointptr,2 ;add array pointer endm
z2cx macro tabofs ;moves z-coordinate to cx mov cx,tabofs + 4
add cx,vz ;add z-translation mov bx,meanptr ;note in Mean-Array add mean[bx],cx
endm
xrot macro zcoord,qcoord ;rotates qcoord by x, stores in zcoord .386
mov bp,crotx ;get angle mov bx,[qcoord]
shl bx,3 ;x8, to align to point entries mov Pointptr,bx
sub bx,[qcoord] ;insg. x6, to align to world entries sub bx,[qcoord]
add bx,offset worldconst ;set to world mov ax,[bx] ;get x
mov zcoord,ax ;and set unchanged mov ax,[bx+2] ;get y
imul w ds:[bp+60d] ;*cos rotx shrd ax,dx,14d
mov cx,ax ;store in cx mov ax,[bx+4] ;get z imul w ds:[bp] ;*-sin rotx shrd ax,dx,14d
sub cx,ax
mov zcoord+2,cx ;y value finished and set mov ax,[bx+2] ;get y
imul w ds:[bp] ;*sin rotx shrd ax,dx,14d
mov cx,ax ;store in cx mov ax,[bx+4] ;get z imul w ds:[bp+60d] ;*cos rotx shrd ax,dx,14d
add cx,ax mov zcoord+4,cx endm
yrot macro zcoord,qcoord ;rotates qcoord by y, stores in zcoord mov bp,croty ;get angle
mov ax,qcoord+2 ;get y
mov zcoord+2,ax ;and set unchanged mov ax,qcoord ;get x
imul w ds:[bp+60d] ;*cos roty shrd ax,dx,14d
mov cx,ax ;store in cx mov ax,qcoord+4 ;get z imul w ds:[bp] ;*sin roty shrd ax,dx,14d
add cx,ax
mov zcoord,cx ;x value finished and set mov ax,qcoord ;get x
imul w ds:[bp] ;*-sin roty shrd ax,dx,14d
mov cx,ax ;store in cx mov ax,qcoord+4 ;get z imul w ds:[bp+60d] ;*cos roty shrd ax,dx,14d
sub ax,cx mov zcoord+4,ax endm
zrot macro zcoord,qcoord ;rotates qcoord by z, saves in zcoord mov bx,Pointptr ;prepare entry in 3D-Point-Array mov bp,crotz ;get angle
mov ax,qcoord+4 ;get z
mov zcoord+4,ax ;and set unchanged mov Point3d[bx+4],ax ;also note in 3D-Array mov ax,qcoord ;get x
imul w ds:[bp+60d] ;*cos rotz shrd ax,dx,14d
mov cx,ax ;store in cx mov ax,qcoord+2 ;get y imul w ds:[bp] ;*-sin rotz shrd ax,dx,14d
sub cx,ax
mov zcoord,cx ;x value finished and set mov Point3d[bx],cx
mov ax,qcoord ;get x imul w ds:[bp] ;*sin rotz shrd ax,dx,14d
mov cx,ax ;store in cx mov ax,qcoord+2 ;get y imul w ds:[bp+60d] ;*cos rotz shrd ax,dx,14d
add cx,ax mov zcoord+2,cx mov Point3d[bx+2],cx endm
get_normal macro ;calculates normal vector of an area mov ax,delta1[2] ;a2*b3
imul delta2[4] shrd ax,dx,4 mov n[0],ax
mov ax,delta1[4] ;a3*b2 imul delta2[2]
shrd ax,dx,4 sub n[0],ax
mov ax,delta1[4] ;a3*b1 imul delta2[0]
shrd ax,dx,4 mov n[2],ax
mov ax,delta1[0] ;a1*b3 imul delta2[4]
shrd ax,dx,4 sub n[2],ax
mov ax,delta1[0] ;a1*b2 imul delta2[2] shrd ax,dx,4 mov n[4],ax mov ax,delta1[2] imul delta2[0] shrd ax,dx,4
sub n[4],ax ;cross product (=normal vector) finished mov ax,n[0] ;x1 ^ 2 imul ax mov bx,ax mov cx,dx mov ax,n[2] ;+x2 ^ 2 imul ax add bx,ax adc cx,dx mov ax,n[4] ;+x3 ^ 2 imul ax add ax,bx
adc dx,cx ;sum in dx:ax push si
call root ;root in ax pop si
mov n_amnt,ax ;amount of normal vector finished endm
light macro ;determines brightness of an area mov ax,n[0]
imul l[0] ;light vector * normal vector mov bx,ax ;form sum in cx:bx
mov cx,dx mov ax,n[2] imul l[2] add bx,ax adc cx,dx mov ax,n[4] imul l[4]
add ax,bx ;scalar product finished in dx:ax adc dx,cx
idiv l_amnt ;divide by l_amnt mov bx,n_amnt ;and by n_amnt cwd
shld dx,ax,5 ;values from -32 bis +32 shl ax,5d
mov bp,startpoly ;prepare addressing of surface color idiv bx ;division by denominator
inc ax or ax,ax
js turned_toward ;if cos à positive -> turned away from the light xor ax,ax ;thus, no light
turned_toward:
sub b polycol,al ;cos<0 -> add to primary color endm code segment assume cs:code,ds:data public drawworld public linecount public polycol public polyn public poly2d public poly3d linecount dw 0
polycol dw 3 ;current surface color polyn dw 0 ;number of existing corners poly2d dw num_cor*4 dup (0) ;corners of polygon to be drawn poly3d dw num_cor*4 dup (0) ;3D corners
public Txt_No
Txt_No dw 0 ;current texture number public delta1,delta2
delta1 dw 0,0,0 ;plane vectors delta2 dw 0,0,0
l dw 11d,11d,11d ;light vector
l_amnt dw 19d ;amount of light vector
drawworld proc pascal ;draws three-dimensional world push ds
push es push bp
lea si,surfcconst ;surfaces are addressed by si mov meanptr,0 ;start in Mean-Array with 0 mov ax,ds:[rotx] ;get angle,
shl ax,1 ;convert as memory offset add ax,offset sine
mov crotx,ax ;and store in help variables mov ax,ds:[roty] ;exactly the same for y shl ax,1
add ax,offset sine mov croty,ax
mov ax,ds:[rotz] ;and z shl ax,1
add ax,offset sine mov crotz,ax
npoly: ;polygon loop mov startpoly,si ;store for later use add si,2 ;skip color
mov cx,[si] ;get number of corners mov linecount,cx ;load counter
inc cx ;due to closed area mov w polyn,cx ;enter in Point-Array add si,2 ;move to actual coordinates nline:
xrot rotx_x,si ;rotate coordinates by x yrot roty_x,rotx_x ;by y
zrot rotz_x,roty_x ;and by z z2cx rotz_x ;get z start setcoord rotz_x,160 ;write coordinates setcoord rotz_y,100
add si,2 ;next corner point dec linecount ;advance line counter je polyok ;all drawn -> terminate jmp nline ;otherwise next line
polyok:
mov bx,meanptr ;calculate mean value: mov ax,mean[bx] ;get sum
mov cx,polyn dec cx cwd
div cx ;and divide by number of corners mov mean[bx],ax ;write back
mov ax,startpoly ;write "number" of area also mov mean[bx+2],ax
add meanptr,4 ;continue
cmp w [si+2],0 ;polygons all finished ? je finished
jmp npoly
finished:
cmp b sf_sort,0 ;sort surfaces ? je no_quicksort
call quicksort pascal,0,bx ;sort field from 0 to current position
no_quicksort:
mov mean[bx+4],0 ;set termination mov ax,cs ;set destination segment mov es,ax
xor bx,bx ;start with first surface
npoly_draw:
lea di,poly2d ;destination:Poly-Array
mov bp,mean[bx+2] ;get pointer to color and points of surface mov ax,ds:[bp] ;get color and set
mov polycol,ax
mov texture,0 ;Assumption: no texture cmp ah,0ffh ;texture ?
jne no_texture
mov texture,1 ;yes, then set mov b txt_no,al ;note number
no_texture: