A popular component of many demo programs and even a few games is voxel spacing. Using this effect, the observer moves over an imaginary landscape with hills and valleys.
Although this is a very general description, many methods exist for displaying this effect. In fact, there is likely to be as many algorithms as programmers who have already tried this effect. The procedure we show here has no complex three-dimensional images, and relies instead on simple two-dimensional relationships. Basically, the program takes a map and tips it on its side so the observer views it at an angle from above. Altitude can now be represented by vertical lines of varying lengths.
The map, which depicts the high and low points of the landscape, is a simple 320 x 200 image with transitions as smooth as possible. Color 0 represents the lowest point and Color 255 the highest.
In practice, two-dimensional projections depend on a fairly basic principle. As we know, objects farther away appear smaller than objects closer to us. So when viewing a landscape through a frame (i.e., your computer monitor) you'll find that more distant objects can fit in the frame than closer objects because the distant objects are smaller. What you see will always be similar to the following illustration.
Example of a landscape drawn with voxel spacing
The rectangular frame shows the entire existing landscape from a bird's eye view. A trapezoid displays its visible portion. All you need to do now is display this trapezoid on a rectangular screen section. This rectangle must stretch the front horizontally so objects in this area appear larger. Any coordinates refer to a projection on the lower screen-half of Mode X.
The trapezoidal projection is formed by steadily decreasing the number of pixels that fit onto one line. So, the ratio of landscape size to screen-pixel size is continually decremented. The initial value is a 1:1 diagram, with 80 pixels to a line (this resolution is used for speed). As the image is drawn the ratio becomes smaller and smaller, until perhaps a 1:2 diagram is reached and only 40 pixels fit on the frontmost line.
In line-by-line display you must also remember the distance between lines always becomes smaller as you move backward. Therefore, you should continually increase the line spacing when drawing from top to bottom.
The only thing missing now is the altitude information for the image. As we mentioned, this involves taking the color for each pixel and arranging a corresponding number of pixels one on top of the other. The greater the color-value, the higher the pixel grows into a vertical line. This vertical structure also eliminates gaps arising from the ever increasing line-spacing toward the front.
To make the scenery more realistic, you'll need to add bodies of water. Simply take all colors below a certain value and set them to this value. This creates surfaces without a vertical structure.
The easiest way to generate the landscape is to use a fractal generator that can generate plasma-clouds. FRACTINT is used for this purpose. Simply generate a plasma-field of resolution 320 x 200 and then load the palette LANDSCAP.MAP (by pressing CL). The completed image can be saved with qS. The program VOXEL.PAS redraws the scene in a loop in response
to mouse-movement (control is through mouse):
PC
PCunderground
You can find VOXEL.PAS on the companion CD-ROM
{$G+}
Uses Crt,Gif,ModeXLib;
Var x,y:Integer; {Coordinates of the trapezoid}
Procedure Draw_Voxel;external; {$l voxel.obj}
Begin
asm mov ax,0; int 33h End; {reset mouse driver} Init_ModeX; {enable Mode X} LoadGif(‘landsc3’); {load landscape}
x:=195; {define start coordinate} y:=130;
Repeat
ClrX($0f); {clear screen} Draw_Voxel; {draw landscape}
Switch; {activate completed video page} WaitRetrace; {wait for retrace}
asm
mov ax,000bh {Function 0bh: read relative coordinates} int 33h sar cx,2 {Division by 2} sar dx,2 add x,cx add y,dx End; If x < 0 Then x:=0; If x > 130 Then x:=130; If y < 0 Then y:=0; If y > 130 Then y:=130; Until KeyPressed; {until key} TextMode(3);
End.
The central procedure is Draw_Voxel, found in Assembler module VOXEL.ASM: data segment
extrn vscreen:dword ;pointer to landscape data extrn x,y: word ;coordinates of trapezoid extrn vpage:word ;current video page data ends
code segment
assume cs:code,ds:data
;variables with fractional part (lower 8 bits): offst dd 0 ;current offset step dd 0 ;pixel size
row_start dd 0 ;beginning of current row row_step dd 0 ;distance from next row
r_count dw 0 ;counter for depth
shrink dw 0 ;correction on lower screen border row dd 0 ;current screen row number
vpage_cs dw 0 ;video page in the code segment
.386
public Draw_Voxel Draw_Voxel proc pascal
;shows landscape on current video page
;reads data from vscreen starting from position (x/y) mov ax,vpage ;note number of video page mov vpage_cs,ax
push ds
mov ax,0a000h ;load destination segment mov es,ax
mov ax,320 ;calculate offset in landscape imul y
add ax,x
lds si,vscreen ;take data from vscreen add si,ax ;add offset
shl esi,8 ;convert to fixed point number mov offst,esi ;initial values for pixel ... mov row_start,esi ... and row
mov step,100h ;first scaling factor 1 mov row,100*256 ;begin in screen row 100 mov row_step,14040h ;distance of rows 320,25 mov shrink,0 ;first no correction
mov r_count,160 ;number of rows to calculate
The first part initializes several important variables. We should mention Offst and row_start in particular. Both form a fixed-point value in Bits 8-31 and following the decimal point in Bits 0-7), which gives the offset of the current pixel or current line. After each pixel, Offst is incremented by Step, moving you one step further in the landscape; for example if Step equals 080h (= 0.5), a new pixel is read from the landscape every two steps.
row_start and row_step are similar: row_step contains 14040, or 320.25 in decimal notation. After each line this sets row_start to the beginning of the next line and at the same time takes care of the narrowing toward the front, by starting the next line a little farther to the right.
next_y:
mov eax,row ;get current (screen) row number mov ebx,eax ;store
shr eax,8 ;convert to whole number add eax,50 ;50 pixels down
imul eax,80 ;convert to offset
mov di,ax ;store as destination pointer cmp di,199*80 ;screen border exceeded ? jb normal
mov di,199*80 ;yes, then position on last row mov eax,row ;difference to bottom screen border shr eax,8
sub eax,149
mov shrink,ax ;and note as correction normal:
add di,vpage_cs ;add current video page imul ebx,16500 ;multiply row number by 1,007 shr ebx,14 ;calculate * 16500 / 16384 mov row,ebx ;and store
Here the program calculates an offset before each line. The variable row contains the current line on the screen, which is converted to an offset. This line number should not be confused with row_Offst, which represents the position within the landscape. row represents the position on the screen. Even if the calculated offset lies below the screen, drawing must still occur because the vertical lines of this pixel can still project into the screen. The program handles this special case by again placing the destination pointer di in the screen and shrinking the height of the bar (by the number of lines given in shrink).
Now the increasing space between screen lines comes into play. The current y-coordinate of the screen is found in the variable row, which is multiplied here by a factor of 1.007, so the lines continue to spread out as they move forward.
mov bp,80 ;number of pixels per row next_x:
mov esi,offst ;load current pixel offset shr esi,8 ;convert to whole number xor eax,eax
mov al,[si] ;load dot from landscape mov cx,ax ;store
cmp cx,99 ;color (=height) < 100 ja fill_bar
mov ax,99 ;then set to 99
fill_bar:
shl ax,5 ;vanishing point projection: height * 32 xor dx,dx
push bp
mov bp,r_count ;divides by the distance add bp,50
idiv bp pop bp
sub ax,shrink ;perform correction jbe continue ;if <= 0, don’t even draw
For each pixel, the color at the current offset is read and the water level set to a minimum value of 99. The counter z_count, which gives the distance of each line from the observer, is used to calculate the flight perspective. This process will be discussed in Chapter 7. Finally, the height (stored in al) is decremented by the value shrink, so the bottom screen border can be processed.
push di next_fill:
mov es:[di],cl ;Enter color
sub di,80 ;Address next higher line dec al ;Decrement counter jne next_fill ;Continue ? pop di
weiter:
inc di ;Address next byte on screen mov esi,step ;Get step size
add esi,offst ;Add up mov offst,esi ;and rewrite
dec bp ;Next pixel jne next_x
mov esi,row_step ;Move line-start add esi,row_start
mov row_start,esi
mov offst,esi ;Also reload pixel-offset
dec step ;Decrement scaling factor dec z_count ;Line counter continues jne next_y pop ds ret Draw_Voxel endp code ends end
The loop next_Fill now draws the vertical bar of the given size. The program then calculates the offset of the next pixel and, if a new line is beginning, its offset as well. In addition, the step size Step is decremented after each line to create the expansion in the foreground.