• No results found

Wiry Figures: Wireframe Modeling

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: