Copy Link
Add to Bookmark
Report

3d-rotation, perspective projection, fixed point math, sorting

Abe's Demoschool: Part IV

eZine's profile picture
Published in 
AbeDemoschool
 · 7 Dec 2022

Now it's finally time to enter the dark, mysterious world of 3d graphic programming. There are enormous amounts of information available on this subject. I've gone through many files and some books on 3d demo and game programming and in this part I'm going to present the basic math and algorithms that 3d computer graphics are built upon.

3d graphics is simply an extension of 2d graphics. Because of that I'll begin with 2d-graphics.

In 2d, a point is represented by an X and a Y -coordinate, written (X,Y).

Origo is the point where both the X and Y coordinate is 0, (0,0). For simplicity, imagine origo being the middle of the screen.

⁄------------------------------------------ø ¬ 
| | ORIGO | | Half screen Y (100)
| | / | |
| -------------≈---------------> | ¡
| | \ |
| | (0,0) |
¿------------------------------------------Ÿ
√--------------------¥
Half screen X (160)

Physical origo is, as you know, at the upper left corner of the screen. To move the origo to the middle of the screen, just add half the height of the screen to the y-coordinates and half the width of the screen to the x-coordinates of all points you wish to draw.

This effect, or transformation, is called translation. That means moving.

Translation in 2d:

        tx = bx + addx; 
ty = by + addy;

where (bx,by) is the coordinates of the point before the movement. Addx is the distance you wish to move the point in the x-direction and addy is the distance to move the point in the y-direction. (tx,ty) is the coordinates of the moved (translated) point.

To ROTATE a point some angle around any arbitrary point requires pretty messy math and we don't have time for that. But to rotate a point around ORIGO is much simpler.

Rotation around ORIGO in 2d:

        rx = bx * sin(angle) - by * cos(angle); 
ry = by * sin(angle) + by * cos(angle);

where (bx,by) are the base coordinates (coordinates before rotation) and (rx,ry) are the rotated coordinates.

The mathematical functions sin and cos wants the angle in radians, but we are used to count angles in degrees. One revolution in a circle is 360 degrees and it is 2*PI radians. This information tells us that:

        1 degree = (1/360)*2*PI radians

So in the rotation formula above, the angle variable should be (DEGREES/360)*2*PI. If you know ANY math you know that PI is 3.14... ;-)

This is like made for precalculating.

Put it in the arrays sinus & cosinus like this:

        for(i=0;i<360;i++) 
{
sinus[i] = sin(2*PI*i/360);
cosinus[i] = cos(2*PI*i/360);
}

Then the rotation formula becomes:

        rx = bx * sinus[angle] - by * cosinus[angle]; 
ry = by * sinus[angle] + by * cosinus[angle];

This is very much faster than the sin(2*PI*angle/360) calculations.

Instead of using a 360 degree circle we could use a 256 degree circle.
Because a byte can have 256 different values, we could use a byte to hold the angle. Just change the precalculations to:

        for(i=0;i<256;i++) 
{
sinus[i] = sin(2*PI*i/256);
cosinus[i] = cos(2*PI*i/256);
}

This way a quarter of a circle (right angle) becomes 64 "degrees". A half revolution is 128 "degrees" and an entire revolution is 256 "degrees".

Familiar numbers to a computer freak 8^).

The old problem with rotating around an arbitrary point isn't really that hard to solve. Just combine two transformations. First translate all points so that the point you want to be in the center of rotation ends up in origo. Then perform a rotation around origo and finally translate all points back the same distance as the first translation (just change the sign of the movement).

Now the points have been rotated around the arbitrary point.

Taking things to 3D

In 3d there's one extra coordinate, the Z coordinate. The translation transformation in 3D is almost exatly as in 2d:

Translation in 3D:

        tx = bx + addx; 
ty = by + addy;
tz = bz + addz;

(bx,by,bz) is the point before movement and (tx,ty,tz) is the moved point.

ROTATION IN 3D is the very heart of 3d demo and game programming.

It's really not just one 3d-rotation it's more like three 2d-rotations.

First around the X-axis, then around the Y-axis and at last the Z-axis.

Rotation around the X-axis:

        ry = cos(angle_x) * by - sin(angle_x) * bz 
rz = sin(angle_x) * by + cos(angle_x) * bz

Rotation around the Y-axis:

        rx = cos(angle_y) * bx + sin(angle_y) * rz 
rrz = -sin(angle_y) * bx + cos(angle_y) * rz

Rotation around the Z-axis:

        rrx = cos(angle_z) * rx - sin(angle_z) * ry 
rry = sin(angle_z) * rx + cos(angle_z) * ry

Angle_x, angle_y, angle_z are the angles to rotate the point around respective axis. bx,by,bz are the coordinates of the point before rotation (base coordinates).

rx,ry,rz are the coordinates rotated once and rrx,rry,rrz are the coordinates rotated twice. When the rotation is done, all coordinates have been rotated twice.

You may notice that rotation around the Z-axis is the same as rotating in 2d, which is natural because the z-axis points right into the screen.

Before actually beginning to draw the points to the screen, there is one little problem left. The screen is 2-dimensional and our virtual world is 3-dimensional. It has been proven there are problems that no computer ever will be able to solve. Fortunately this is not one of them ...

To project a 3-dimesional world onto a 2-dimensional screen we use perspective projection. In reality things further away looks smaller. That could mean that the z-coordinate should be in the denominator, and that is also the case.

Perspective Projection:

        SX = DIST * X / (DIST + Z) 
SY = DIST * Y / (DIST + Z)

where (X,Y,Z) are the 3d coordinates and (SX,SY) are the 2d screen coordinates.

DIST is the distance from the eye to the screen. For simplicity and speed

I chose DIST = 256.

                       ____ Field Of Vision 
____ | ---
--- |
/| ____ o ---- X
EYE | ---- | OBJECT
\| ___ |
---- | ___
SCREEN ---- FOV
/-------------/
DIST

With the formula above the object will appear on the screen where the straight line between the eye and the object intersects with the screen (at the o sign).

Now we now enough to put a 3d demo together, but it would be slow.

To make the demo faster I'll have to use some optimizations. The first optimization would be to put the sin & cos values in precalculated arrays as I described in the 2d case. The sin & cos calculations would still be slow because they are real numbers. It is possible to use integers instead. That's called fixed point math.

What is fixed point math?

Fixed point math is a way to represent floating point numbers with integers.
You simply use some of the bits for the whole part of the number and some bits for the fractional part.

Ex

⁄-¬-¬-¬-¬-¬-¬-¬-¬-¬-¬-¬-¬-¬-¬-¬-ø 
|0|0|0|0|1|0|0|1|1|1|0|0|0|0|0|0|
¿-¡-¡-¡-¡-¡-¡-¡-.-¡-¡-¡-¡-¡-¡-¡-Ÿ
/--------------//---------------/
whole part fract. part

In this example there are 8 bits for the whole part and 8 bits for the fractional part. That means that the whole part is whole part is 00001001 = 9 and the fractional part is 11000000 = 1/2 + 1/4 = 3/4 = 0.75

This gives that the fixed point number is 9.75.

The largest number representable with 8 bits whole part is 256 and the smallest number with 8 bits fractional part is 1/256 = 0.0039.

When using fixed point math you have to think about how big and how small numbers they are going to represent.

In 386 assembler there are 32-bits registers but if 16 bits are enough, it's faster.

How do I use fixed point math?

First decide how many bits are needed for the fractional part, fracbits.
Then assign the fixed point integers their real value * 2^fracbits.
Now perform operations with the fixed point integers. Addition and subtraction is easy, multiplication and division is a little bit more tricky.

Addition: Add the fixed point integers. Shift the result fracbits to the right to get the actual result (the result is, off course, cut off to an integer).

Ex.
Let's use 4 bits fractional part, 12 bits whole part.
We want to assign the fixed point integer fp1 the value 3.5.

fp1 = 3.5 * 2^4 = 3.5 * 16 = 56 = 000000000011 1000 
3 + 1/2 = 3.5

And assign fp2 the value 2.5.

fp2 = 2.5 * 16 = 40 = 000000000010 1000 
2 + 1/2 = 2.5

Now let

fp3 = fp1 + fp2 = 56 + 40 = 96 = 000000000110 0000 
6 + 0 = 6.

fp3 is now 96. To get the actual value shift fp3 right 4 bits.
fp3 = fp3>>4 => fp3 = 6. And 2.5 + 3.5 is 6. It works.

Subtraction: The same as addition, just subtract the fp numbers and shift the result fracbits to the right.

You can perform several additions and subtractions and hold the shift until you need the result:
Ex: result = (fp1 + fp2 - fp3 + fp4 - fp5) >> fracbits;

Multiplication: In addition you could write fp3 = fp1 + fp2 and the perform the right shift to get the actual result.

In multiplication you have to shift fracbits to the right to get the result as a fixed point:

Ex:

     fp3 = (fp1 * fp2) >> fracbits;  /* fp3 = fp1*fp2 */ 
result = fp3 >> fracbits;

Division: A little bit more tricky than the others. Before performing the division you have to shift the numerator fracbits to the LEFT.
Ex:

     fp3 = (fp1 << fracbits)/fp2;    /* fp3 = fp1/fp2 */ 
result = fp3 >> fracbits;

In assembler, when multiplying two 16 bit integers, the result is in dx:ax = 32 bits. That means that you can multiply two big 16-bit numbers and still not lose any bits, very good. It means that we can represent the sin & cos tables as 16-bit fixed point integers. As you know, sin & cos values vary between -1 and 1 so it's enough to use 2 bits for the whole part and we can use 14 bits for the fractional part, heavy...

I calculated the fixed point sin & cos tables like this with 14 bits shift:

        for(i=0;i<256;i++) 
{
sinus[i] = sin(2*PI*i/256)*16384;
cosinus[i] = cos(2*PI*i/256)*16384;
}

16384 is 214 and PI is 3.1415927.


Now we can put a 3d demo together. I've done a little demo using a rotating cube with 8 sprites at the corners of the cube.

I have hidden all the fixed point math and rotation stuff in the rot.asm file. It only contains one callable function, rotatepoints. Rotatepoints takes 4 parameters: angle_x, angle_y, angle_z and NUM_OF POINTS. The three first are the angles to rotate the points around the respective axis. The last parameter NO_OF_POINTS tells how many points that shall be rotated.

Where are the points that shall be rotated? You may ask.

Well, they are in the global arrays: bx,by,bz. It's really bad programming style to use global arrays but it's faster and simpler and that's what counts.

bx,by,bz contains the base coordinates, they never change. Rotatepoints uses 8 more global arrays: rx,ry,rz, sx,sy and addx,addy,addz.

rx,ry,rz is where the rotated coordinates are stored and sx,sy are where the perspective projected screen coordinates are stored.

Addx, addy and addz is the distance that the rotated coordinates will be translated (moved) away from origo in the respective direction.

If addx, addy and addz are 0 the points will be centered around the middle of the screen.

All you have to do is put the base coordinates in bx,by,bz and call rotatepoints. Then the rotated coordinates will automatically be put in rx,ry,rz and the screen coordinates will be put in sx,sy.

Why are rx,ry,rz necessary? Well, to make it look right on the screen you have to draw the sprites that are furthest away first, and the closest sprites last (painters algorithm). The rz coordinates are necessary to find out how far away into the screen the points are. Actually we're not ready yet.

To be able to draw the sprites in right order we have to sort sx and sy with respect to rz.

Ex:

before: rz = 3,1,2 
sx = 1,2,3
sy = 4,5,6

sort rz and let sx & sy change along with rz

after:  rz = 1,2,3 
sx = 2,3,1
sy = 5,6,4

There are many sorting algorithms, the simplest is the bubblesort algorithm.
I've chosen a faster sorting algorithm called quicksort.

The disadvantage with quicksort is that it is recursive. Recursive means that it calls itself. My quicksort assumes that rz, sx & sy are global.

Quicksort always have at least two parameters: lower boundary and upper boundary. The boundaries are the area in the array that will be sorted.

If lower boundary, lb, is 0 and ub is 5. Then the 6 first numbers in the array will be sorted.

Quicksort takes the first number in the interval that will be sorted inserts it at it's "right" place. Then quicksort calls it self twice. Once with the interval below the "sorted" number and once with the interval above the "sorted" number. Quicksort continues this until the interval is only one number big (ub -lb) <= 1; Then it's done and all numbers are sorted.

This may sound strange but it's one of the most effective sorting algorithms.

My version of quicksort is written in assembler,
in C it would look something like this:

void qsort3d(int lb,int ub) 
{
int a,down,up,hold,indexA;

indexA=lb; /* set the index of a to lower boundary */
a=rz[indexA]; /* a = the first number in the interval to be sorted */
down=lb; /* let down point at the beginning of the interval */
up=ub; /* let up point at the end of the interval */

while(down<up)
{
while((rz[down]<=a)&&(down<=ub)) down++; /* inc down until rz[down]>a */
while((rz[up]>a)&&(up>=lb)) up--; /* dec up until rz[up]<=a */

if(up>down)
{
hold=rz[down]; /* if up > down xchange rz[down] & rz[up] */
rz[down]=rz[up];
rz[up]=hold;

hold=sx[down]; /* xchange sx[down] & sx[up] */
sx[down]=sx[up];
sx[up]=hold;
hold=sy[down]; /* and xchange sy[down] & sy[up] the same way */
sy[down]=sy[up];
sy[up]=hold;
}
}
hold=rz[indexA]; /* xchange rz[indexA] and rz[up] */
rz[indexA]=rz[up];
rz[up]=hold;

hold=sx[indexA]; /* exchange sx and sy */
sx[indexA]=sx[up];
sx[up]=hold;
hold=sy[indexA]; /* the same way */
sy[indexA]=sy[up];
sy[up]=hold;

indexA=up;

/* recursive calls to qsort3d with smaller intervals */
if(indexA-1-lb>0) qsort3d(lb,indexA-1);/* if smaller intervals > 1 element*/
if(ub-indexA-1>0) qsort3d(indexA+1,ub);/* sort the smaller intervals too */
}

Now we must be ready to make a 3d demo or?
YES it's about time. All the ingredients for a pretty fast 3d-demo are here.
Now it's up to you too cook'em.
There are of course more optimizations to do but I've done the most obvious optimizations for you.

What you can begin with is to make some nice data structures.
These global data structures that I've made now really sux and must be improved when we move on to drawing filled polygons with shading and eliminate hidden planes.

Rotatepoints and qsort3d uses these global arrays:

bx,by,bz           Base coordinates that don't change 
rx,ry,rz Rotated coordinates
sx,sy Screen coordinates
addx,addy,addz Distance to move the object from origo (usuallly 0)

Iv'e split the demo into these files:

demo4.h       Contains all extern declarations of functions in the .asm files 
demo4.c basic 3d example demo
d4morf.c example demo that performs morfing on the base objects
rot.asm Contains the Rotatepoints and qsort3d functions
graph.asm Contains the sprite & graphic funtions from part III (and some new ones)

demo4 and d4morf are two different demos but they use the same functions (and they have the same global arrays)

Next time we'll continue this journey into 3d space with drawing solid polygons and hidden surface elimination.

1996-02-07 Abe of Space

mail: dat94avi@bilbo.mdh.se

s-mail:
Albert Veli
Spisringsg. 9
724 76 VÑsterÜs
SWEDEN

ROT.ASM

assembler source for the rotation routines

; ROT.ASM contains one function, _RotatePoints, that can be called from C. 
; To do this, the C program must have some global variables
; these variables are defined as extrn here in the asm source.
; They are _bx, _by, _bz that should contain the coordinates of the object
; to be rotated. They are never changed instead the rotated coordinates
; are stored in _rx, _ry, _rz. _sx and _sy will contain the screen coordinates
; _addx, _addy, _addz are the distance the object will be moved in the x,y,z
; direction. If they are 0 the object will appear at the center of the screen.
; ROT.ASM also contains two local routines that are called from _RotatePoints.

P386N ;allow 386 specific instructions (ie movsd)
IDEAL
MODEL small ;data < 64k, program < 64k
DATASEG

EXTRN _bx,_by,_bz,_rx,_ry,_rz,_sx,_sy ;extrn variables & arrays
EXTRN _addx, _addy, _addz ;must be defined elsewhere (in c)

sinax dw ? ;the sin & cos variables are only
sinay dw ? ;used here, in the asm source
sinaz dw ?
cosax dw ?
cosay dw ?
cosaz dw ?

HALFX EQU 320/2 ;Constants
HALFY EQU 200/2

;This is the precalculated sin & cos table
;It's the sin & cos values of a 256 "degree" circle with each value
;multiplied by 2^14 (we use fixed point math with 14 bits shift).
;The cosine values are the same as (sinevalues + 90 degrees). 90 deg in
;a 360 deg circle is the same as 64 "deg" in a 256 "deg" circle. That means
;that after 64 values in sintab, the costab & sintab can share the same values
sintab dw 0, 402, 804, 1205, 1606, 2006, 2404, 2801, 3196, 3590, 3981, 4370, 4756, 5139, 5520, 5897
dw 6270, 6639, 7005, 7366, 7723, 8076, 8423, 8765, 9102, 9434, 9760, 10080, 10394, 10702, 11003, 11297
dw 11585, 11866, 12140, 12406, 12665, 12916, 13160, 13395, 13623, 13842, 14053, 14256, 14449, 14635, 14811, 14978
dw 15137, 15286, 15426, 15557, 15679, 15791, 15893, 15986, 16069, 16143, 16207, 16261, 16305, 16340, 16364, 16379
costab dw 16384, 16379, 16364, 16340, 16305, 16261, 16207, 16143, 16069, 15986, 15893, 15791, 15679, 15557, 15426, 15286
dw 15137, 14978, 14811, 14635, 14449, 14256, 14053, 13842, 13623, 13395, 13160, 12916, 12665, 12406, 12140, 11866
dw 11585, 11297, 11003, 10702, 10394, 10080, 9760, 9434, 9102, 8765, 8423, 8076, 7723, 7366, 7005, 6639
dw 6270, 5897, 5520, 5139, 4756, 4370, 3981, 3590, 3196, 2801, 2404, 2006, 1606, 1205, 804, 402
dw 0, -402, -804, -1205, -1606, -2006, -2404, -2801, -3196, -3590, -3981, -4370, -4756, -5139, -5520, -5897
dw -6270, -6639, -7005, -7366, -7723, -8076, -8423, -8765, -9102, -9434, -9760, -10080, -10394, -10702, -11003, -11297
dw -11585, -11866, -12140, -12406, -12665, -12916, -13160, -13395, -13623, -13842, -14053, -14256, -14449, -14635, -14811, -14978
dw -15137, -15286, -15426, -15557, -15679, -15791, -15893, -15986, -16069, -16143, -16207, -16261, -16305, -16340, -16364, -16379
dw -16384, -16379, -16364, -16340, -16305, -16261, -16207, -16143, -16069, -15986, -15893, -15791, -15679, -15557, -15426, -15286
dw -15137, -14978, -14811, -14635, -14449, -14256, -14053, -13842, -13623, -13395, -13160, -12916, -12665, -12406, -12140, -11866
dw -11585, -11297, -11003, -10702, -10394, -10080, -9760, -9434, -9102, -8765, -8423, -8076, -7723, -7366, -7005, -6639
dw -6270, -5897, -5520, -5139, -4756, -4370, -3981, -3590, -3196, -2801, -2404, -2006, -1606, -1205, -804, -402
dw 0, 402, 804, 1205, 1606, 2006, 2404, 2801, 3196, 3590, 3981, 4370, 4756, 5139, 5520, 5897
dw 6270, 6639, 7005, 7366, 7723, 8076, 8423, 8765, 9102, 9434, 9760, 10080, 10394, 10702, 11003, 11297
dw 11585, 11866, 12140, 12406, 12665, 12916, 13160, 13395, 13623, 13842, 14053, 14256, 14449, 14635, 14811, 14978
dw 15137, 15286, 15426, 15557, 15679, 15791, 15893, 15986, 16069, 16143, 16207, 16261, 16305, 16340, 16364, 16379

CODESEG

PUBLIC _RotatePoints ;this function can be called from C
PUBLIC _qsort3d ;sorts sx & sy in "rz-order"

; qsort(int low_boundary, int upper_boundary);
PROC _qsort3d NEAR
ARG lb:Word, ub:Word ;lower & upper boundary

; down = BX, up = SI index = DI a=DX

push bp
mov bp,sp
push si
push di
push ax

mov di,[lb] ;/* set index to lower boundary */
shl di,1 ;integer arrays
mov ax,[_rz+di]
shr di,1
mov dx,ax ; a=rz[indexA];
mov bx,[lb] ; down=lb;
mov si,[ub] ; up=ub;

@@lp1:
; while((rz[down]<=a)&&(down<=ub)) down++;
shl bx,1
mov ax,[_rz+bx]
shr bx,1
cmp ax,dx
jg @@s1
cmp bx,[ub]
jg @@s1
inc bx
jmp @@lp1
@@s1:
; while((rz[up]>a)&&(up>=lb)) up--;
shl si,1
mov ax,[_rz+si]
shr si,1
cmp ax,dx
jle @@s2
cmp si,[lb]
jl @@s2
dec si
jmp @@s1
@@s2:
cmp si,bx
jle @@s3 ;if up > down xchange rz[up] and rz[down]
shl bx,1
shl si,1
mov cx,[_rz+bx]
mov ax,[_rz+si]
mov [_rz+bx],ax
mov [_rz+si],cx
mov cx,[_sx+bx]
mov ax,[_sx+si]
mov [_sx+bx],ax
mov [_sx+si],cx
mov cx,[_sy+bx]
mov ax,[_sy+si]
mov [_sy+bx],ax
mov [_sy+si],cx
shr bx,1
shr si,1
@@s3:
cmp bx,si
jl @@lp1
shl di,1
shl si,1
mov cx,[_rz+di]
mov ax,[_rz+si]
mov [_rz+di],ax
mov [_rz+si],cx
mov cx,[_sx+di]
mov ax,[_sx+si]
mov [_sx+di],ax
mov [_sx+si],cx
mov cx,[_sy+di]
mov ax,[_sy+si]
mov [_sy+di],ax
mov [_sy+si],cx
shr di,1
shr si,1

mov di,si ;indexA=up;

push [ub] ;assume that conditions below is true and push
inc di ;parameters to recursive call
mov ax,di
push di
dec di
dec di
push di
push [lb]
; if(indexA-1-lb > 0) quicksort(lb,indexA-1);
; if(ub-(indexA+1) > 0) quicksort(indexA+1,ub);
sub di,[lb]
jle @@s4
call _qsort3d
@@s4: pop [lb] ;pop used parameters off the stack
pop di
mov cx,[ub]
sub cx,ax
jle @@s5
call _qsort3d
@@s5: pop di
pop [ub]

pop ax
pop di
pop si
pop bp
ret
ENDP _qsort3d



;RotPoint rotates one point using the precalculated values stored in
;sinax,sinay,sinaz,cosax,cosay and cosaz
;Destroys: ax,edx,si,edi,es
;Input: bx= X cx= Y bp= Z
;Output: bx= rotatedX cx= rotatedY bp= rotatedZ
PROC RotPoint NEAR

; Rotate point around the X-axis
; ry = cos(angle x) * y - sin(angle x) * z
; rz = sin(angle x) * y + cos(angle x) * z
mov ax,[cosax] ; ax = cos(angle x)
imul cx ; dx:ax = cos(angle x) * y
mov di,dx ; dx:ax means that the high word of the multiplication
shl edi,16 ; is in dx and the low word in ax
mov di,ax ; edi = by * cos(angle x)
mov ax,[sinax] ; ax = sin(angle x)
imul bp ; ax = sin(angle x) * z
shl edx,16
mov dx,ax ; edx = bz * sinax
sub edi,edx ; edi=edi-edx= cos(angle x)*y - sin(angle x)*z
sar edi,14 ; shift to the actual value (fixed point math)
mov es,di ; save rotated y in es
mov ax,[sinax] ; ax = sin(angle x)
imul cx ; dx:ax = sin(angle x) * y
mov di,dx
shl edi,16
mov di,ax ; edi = sin(angle x) * y
mov ax,[cosax] ; ax = cos(angle x)
imul bp ; dx:ax = cos(angle x) * z
shl edx,16
mov dx,ax ; edx = cos(angle x) * z
add edi,edx ;edi=edi+edx = sin(angle x)*y + cos(angle x)*z
sar edi,14 ;shift right => di = actual value of rotated z
mov cx,es ; cx = ry (rotated y)
mov bp,di ; bp = rz (rotated z)

; Rotate point around the Y-axis
; rx = cos(angle y) * x + sin(angle y) * rz
; rrz = -sin(angle y) * x + cos(angle y) * rz
mov ax,[cosay] ; ax = cos(angle y)
imul bx ; dx:ax = cos(angle y) * x
mov di,dx
shl edi,16
mov di,ax ; edi = cos(angle y) * x
mov ax,[sinay] ; ax = sin(angle y)
imul bp ; dx:ax = sin(angle y) * rz
shl edx,16
mov dx,ax ; edx = sin(angle y) * rz
add edi,edx ;edi=edi+edx= cos(angle y)*x + sin(angle y)*rz
sar edi,14 ; apply fixed point math
mov es,di ; save rx (rotated x) in es
mov ax,[sinay] ; ax = sin(angle y)
neg ax ; ax = -sin(angle y)
imul bx ; dx:ax = -sin(angle y) * x
mov di,dx
shl edi,16
mov di,ax ; edi = -sin(angle y) * x
mov ax,[cosay] ; ax = cos(angle y)
imul bp ; dx:ax = cos(angle y) * rz
shl edx,16
mov dx,ax ; edx = cos(angle y) * rz
add edi,edx ;edi=edi-edx=-sin(angle y)*x + cos(angle y)*rz
sar edi,14 ;fp math, di = rrz (rotated rz)
mov bx,es ; bx = rx (rotated x)
mov bp,di ; bp = rrz (rotated rotated z)

; Rotate point around the Z-axis
; rrx = cos(vz) * rx - sin(vz) * ry
; rry = sin(vz) * rx + cos(vz) * ry
mov ax,[cosaz] ; ax = cos(angle z)
imul bx ; dx:ax = cos(angle z) * rx
mov di,dx
shl edi,16
mov di,ax ;edi = cos(angle z) * rx
mov ax,[sinaz] ; ax = sin(angle z)
imul cx ; dx:ax = sin(angle z) * ry
shl edx,16
mov dx,ax ; edx = sin(angle z) * ry
sub edi,edx ;edi=edi-edx=cos(angle z)*rx - sin(angle z)*ry
sar edi,14 ; shift right to actual value
mov es,di ; es = rrx
mov ax,[sinaz] ; ax = sin(angle z)
imul bx ; dx:ax = sin(angle z) * rx
mov di,dx
shl edi,16
mov di,ax ; edi = sin(angle z) * rx
mov ax,[cosaz] ; ax = cos(angle z)
imul cx ; dx:ax = cos(angle z) * ry
shl edx,16
mov dx,ax ; edx = cos(angle z) * ry
add edi,edx ;edi=edi+edx=sin(angle z)*rx + cos(angle z)*ry
sar edi,14 ; shift right to actual value
mov bx,es ; bx = rrx (rotated rx)
mov cx,di ; cx = rry (rotated ry)

ret
ENDP RotPoint


;CalcSxSy Calculates the Screen (x,y) coordinates using a perspective
;projection on the (x,y,z) coordinates. Screen distance is 256.
;CalcSxSy also adds half width of the screen to the sx coordinate and half
;height of the screen to the sy coordinate to make the coordinates
;centered around the center of the screen.
;Destroys: ax,dx,bp
;Input: bx= X cx= Y bp= Z
;Output: bx= SX, cx =SY
PROC CalcSxSy NEAR
; Perspective Projection:
; SX = DIST * X / (DIST + Z)
; SY = DIST * Y / (DIST + Z)
movsx eax,bx ; eax = x-coord. with Sign eXtension
shl eax,8 ; eax = 256*x (distance is 256)
mov edx,eax ; edx = 256*x
shr edx,16 ; dx:ax = 256*x (DIST * X)
add bp,256 ; bp = DIST + Z
idiv bp ; quote of division => ax
add ax,HALFX ; add ax with 320/2 (half width of screen)
mov bx,ax ; bx = 256*x / (256 + z) + 160

movsx eax,cx ; eax = y-coord. with Sign eXtension
shl eax,8 ; eax = 256*y
mov edx,eax
shr edx,16 ; dx:ax = 256*y (DIST * Y)
idiv bp ; divide with (DIST + Z)
add ax,HALFY ; add result of division with half screen height
mov cx,ax ; cx = 256*y/(256 + z) + 100
ret
ENDP CalcSxSy

;_RotatePoints rotates NUM_OF_PTS points in (_bx,_by,_bz) (rotx,roty,rotz) degrees,
;adds addx,addy,addz to the coordinates, stores them in _rx, _ry & _rz
;and finally calculates the screen coordinates and stores them in _sx and _sy.
;*********** Call from c with: ***************
;*** RotatePoints(ax, ay, az, num_of_pts); ***
;*********************************************
PROC _RotatePoints NEAR
ARG rotx:Byte, roty:Byte, rotz:Byte, num_of_pts:Word

push bp ;this bp,sp trick must be here if
mov bp,sp ;you use arguments (has to do with the stackframe)
pushad ;save all registers

mov bl,[rotx]
xor bh,bh
shl bx,1 ;bx = index in sintab and costab
mov ax,[sintab + bx] ;move the appropriate value from the sintab
mov [sinax],ax ; to sinax
mov ax,[costab + bx] ; and the appropriate value from costab
mov [cosax],ax ; to cosax
mov bl,[roty] ; do the same with sinay & cosay
xor bh,bh
shl bx,1
mov ax,[sintab + bx]
mov [sinay],ax
mov ax,[costab + bx]
mov [cosay],ax
mov bl,[rotz] ; and sinaz & cosaz
xor bh,bh
shl bx,1
mov ax,[sintab + bx]
mov [sinaz],ax
mov ax,[costab + bx]
mov [cosaz],ax

mov di,[num_of_pts] ;di = loopcounter
@@rotloop: ;loop through all points in the arrays
dec di ;let di be both loopcounter and index in the arrays
shl di,1 ;di = di * 2 because the arrays are type int
mov bx,[_bx + di] ;load cordinates to rotate
mov cx,[_by + di]
mov bp,[_bz + di]

push di
call RotPoint ;rotate the coordinates of one point
pop di

add bx,[_addx] ;add "movement" to the rotated coordinates
add cx,[_addy]
add bp,[_addz]

mov [_rx + di],bx ;save rotated coordintates in _rx, _ry & _rz
mov [_ry + di],cx
mov [_rz + di],bp
call CalcSxSy ;calculate the screen coordinates
mov [_sx + di],bx ;and save them in _sx and _sy
mov [_sy + di],cx

shr di,1 ;shift di back into acting as loopcounter
or di,di ;is di 0 yet?
jnz @@rotloop ;if not, rotate another point

popad ;restore the saved registers
pop bp
ret
ENDP _RotatePoints


END


GRAPH.ASM

sprite routines and more (almost same as asmdemo3.asm)

;********************w*h*o**r*u*l*e*s**i*t**a*l*l*?************************* 
;**** ****
;**** To assemble write tasm /ml asmdemo3 ****
;**** C-program is compiled with bcc -c demo3.c ****
;**** link with bcc -ms demo3.obj asmdemo3.obj ****
;**** ( -ms means model small ) ****
;**** ****
;**** To debug with td, build everything with: ****
;**** bcc -v demo3.c asmdemo3.asm ****
;**** Then debug with: td demo3 ****
;**** ****
;**** Albert Veli 96-01-12 ****
;**** mail: dat94avi@bilbo.mdh.se ****
;**** ****
;*********************s*m*a*l*l**s*m*a*l*l**s*m*a*l*l***********************

P386N ;allow 386 instr. (movsd)
IDEAL
MODEL small

DATASEG

foo dw ? ;foo & bar are temporary variables
bar dw ? ;they are used if the registers aint enough
string db 2000 dup (?) ;used by outstr

CODESEG
;functions begins with _ then the name
PUBLIC _show32spr ;call with show32spr(x,y,sprite,dest);
PUBLIC _showtrans ; ' ' showtrans(x,y,sprite,dest);
PUBLIC _runnyspr ; ' ' runnyspr(x,y,sprite,dest);
PUBLIC _erase32bit ; ' ' erase32bit(x,y,width,height,dest);
PUBLIC _getback32 ; ' ' getback32(x,y,width,height,source,dest);
PUBLIC _wtsync ; ' ' wtsync();
PUBLIC _setmode ; ' ' setmode(grafixmode);
PUBLIC _blackpal ; ' ' blackpal();
PUBLIC _setpal ; ' ' setpal(palett);
PUBLIC _putpixel ; ' ' putpixel(x,y,color,dest);
PUBLIC _getpixel ; ' ' color=getpixel(x,y,dest);
PUBLIC _getch ; ' ' getch();
PUBLIC _kbhit ; ' ' kbhit();
PUBLIC _loadpalett ; ' ' loadpalett("name", pal);
PUBLIC _loadsprite ; ' ' loadsprite("name", spr);
PUBLIC _showpic ; ' ' showpic("name",x,y);
PUBLIC _flip64k ; ' ' flip64k(source,dest);
PUBLIC _disflip64k ; ' ' disflip64k(source,dest);
PUBLIC _disflipblack ; ' ' disflipblack(source,dest);
PUBLIC _malloc ; ' ' virt=malloc(64000);
PUBLIC _free ; ' ' free(virt);
PUBLIC _outstr
PUBLIC _screenadr


PROC _screenadr NEAR
xor ax,ax
mov dx,0a000h
ret
ENDP _screenadr

;outstr prints a string on the screen using dos int 21h function 9
;The string ends with $ or 0 (if 0, it is changed to a $)
;void outstr(char far*str);
PROC _outstr NEAR
ARG str:Dword
push bp
mov bp,sp
push ds ;save these registers
push si
push di
lds si,[str] ;ds:si = str
mov di,OFFSET string
mov dx,di
mov ax,SEG string ;es:di = string
mov es,ax
@@lp: mov al,[si] ;copy str to local array string
mov [es:di],al
cmp al,0 ;copy until a 0 or a $ sign appears
jz @@ch
cmp al,'$'
jz @@print
inc si
inc di
jmp @@lp
@@ch: mov al,'$' ;if it was 0 replace with $ in string
mov [es:di],al
@@print:push es
pop ds ;ds:dx = beginning of string
mov ah,9 ;call dos int 21 func 9, print string
int 21h
pop di ;restore di, si, ds & bp
pop si
pop ds
pop bp
ret
ENDP _outstr


;************************************************************************
;* void runnyspr(int x,int y,char far*sprite,char far*dest); *
;* Register: ax,bx,cx,dx *
;* width on dest must be 320 bytes *
;* shows sprite with a "runny-effect" *
;************************************************************************
PROC _runnyspr NEAR
ARG xpos:Word, ypos:Word, sprite:Dword, dest:Dword
push bp
mov bp,sp
push ds
push si
push di
lds si,[sprite] ;ds:si = sprite
les di,[dest] ;es:di = destination
mov di,[xpos]
lodsw ;height to ax
mov [foo],ax ;height to foo
cmp ax,0
jz @@error ;if height 0 draw nothing
lodsw ;width to ax
mov cx,ax ;save width in cx
mul [foo] ;calculate size of sprite
sub ax,cx ;minus one row
add si,ax ;ds:si now points to the last row in sprite

mov bx,320 ;bx = pix/screenrow
sub bx,cx ;bx = bytes to next row on the screen
mov ax,cx ;ax = width of sprite

mov [bar],0FFFFh

;draw the sprite with each row falling from the top of the screen down to
;it's final position, beginning with the last row
@@Lp1: mov di,[xpos] ;begin at the top of the screen with each row in sprite
mov dx,[ypos] ;dx = top position of sprite
add dx,[foo] ;dx = final y-position of current row
@@Lp2: dec [bar] ;throw in a pause every 32:nd time
mov cx,[bar]
and cx,31
jnz @@Skip ;skip this row to study the effect in slow motion
call _wtsync ;throw in a pause
@@Skip: mov cx,ax ;cx = number of pixels in one row (width)
rep movsb ;draw one row
sub si,ax ;point si back to the beginning of the row (in sprite)
add di,bx ;di := next row on screen
dec dx
jnz @@Lp2 ;loop until the row reaches it's final
;position on the screen
sub si,ax ;si = row above the one just drawn in sprite
dec [foo]
jnz @@Lp1 ;loop through all rows

@@error:
pop di
pop si
pop ds
pop bp
ret
ENDP _runnyspr



;**************************************************************************
;*** int loadpalett(char far*name,char far*pal); ***
;*** Destroys: ax,bx,cx,dx return:1 OK 0:error ***
;**************************************************************************
PROC _loadpalett NEAR
ARG name:Dword, pal:Dword
push bp
mov bp,sp
push ds
lds dx,[name] ;ds:dx points to the filename
mov ax,3d00h ;open for reading only
int 21h ;open
jc @@errr ;if error jump to errr
mov bx,ax ;bx=filehandle
lds dx,[pal] ;read to pal
mov cx,256*3 ;read the entire palette to pal
mov ah,3fh ;dos read file
int 21h
jc @@errr
mov ah,3eh ;dos close file
int 21h
mov ax,1 ;ax = 1, no error
jmp @@exit
@@errr: xor ax,ax ;ax = 0 error
@@exit: pop ds
pop bp
ret
ENDP _loadpalett

;**************************************************************************
;*** int loadsprite(char far*name,char far*spr); ***
;*** Destroys: ax,bx,cx,dx return:1 OK 0:error ***
;**************************************************************************
PROC _loadsprite NEAR
ARG name:Dword, spr:Dword
push bp
mov bp,sp
push ds
push si
lds dx,[name] ;ds:dx points to the filename
mov ax,3d00h ;open for reading
int 21h ;open
jc @@err ;if error, jump to err
mov bx,ax ;bx=filehandle
lds dx,[spr] ;read to spr
mov cx,4 ;read the head of spr
mov ah,3fh ;dos read file
int 21h
jc @@err
mov si,dx
mov ax,[si] ;height to ax
inc dx
inc dx
mov si,dx
mov cx,[si]
xchg ax,cx
mul cl ;ax=height*width
mov cx,ax
mov ax,3f00h ;dos readfile
inc dx
inc dx
int 21h
jc @@err
mov ah,3eh ;dos close file
int 21h
mov ax,1 ;ax = 1, no error
jmp @@exit
@@err: xor ax,ax ;ax = 0 error
@@exit: pop si
pop ds
pop bp
ret
ENDP _loadsprite


;**************************************************************************
;*** void showpic( int x,int y, char far*name, char far*dest); ***
;*** Destroys: ax,bx,cx,dx return:1 OK 0:error ***
;*** Shows a pic from file on dest (screen or virt) ***
;*** !CAUTION width of dest is assumed to be 320 bytes CAUTION! ***
;**************************************************************************
PROC _showpic NEAR
ARG xpos:Word, ypos:Word, name:Dword, dest:Dword
push bp
mov bp,sp
push ds
push si
push di
les di,[dest]
mov bx,[xpos]
mov ax,[ypos]
xchg ah,al ;ax=256*y
add bx,ax ;bx=256*y+x
shr ax,2 ;ax=64*y
add bx,ax ;bx=320*y+x offset to di
add di,bx ;es:di = destination (screen or virt)
lds dx,[name] ;ds:dx point to filename
mov ax,3d00h ;open for reading
int 21h ;open
jc @@err ;if error, jump to err
mov bx,ax ;bx=filehandle
mov ax,es
mov ds,ax ;ds=segment of destination
mov dx,di ;read to destination
mov cx,4 ;read the head of pic
mov ah,3fh ;dos read file
int 21h
jc @@err
mov si,dx
mov ax,[si] ;height to ax
inc dx
inc dx
mov si,dx
mov cx,[si] ;width to cx
@@lop: shl eax,16 ;save height in high word of eax
mov ax,3f00h ;dos readfile
mov dx,di
int 21h
jc @@err
add di,320
shr eax,16 ;shift height back to ax
dec ax
jnz @@lop
mov ah,3eh ;dos close file
int 21h
mov ax,1 ;ax = 1, no error
jmp @@exit
@@err: xor ax,ax ;ax = 0 error
@@exit: pop di
pop si
pop ds
pop bp
ret
ENDP _showpic


;************************************************************************
;* void putpixel(int x, int y,char color,char far*dest); *
;* Register: ax *
;************************************************************************
PROC _putpixel NEAR
ARG xpos:Word, ypos:Word, color:Byte, dest:Dword
push bp
mov bp,sp
push di
les di,[dest]
mov di,[xpos]
mov ax,[ypos]
xchg ah,al ;ax=256*y
add di,ax ;di=256*y+x
shr ax,2 ;ax=64*y
add di,ax ;di=320*y+x offset to di
mov al,[color]
mov [es:di],al
pop di
pop bp
ret
ENDP _putpixel


;************************************************************************
;*** char getpixel(int x, int y,char far*src); ***
;*** Register: ax return:color ***
;************************************************************************
PROC _getpixel NEAR
ARG xpos:Word, ypos:Word ,src:Dword
push bp
mov bp,sp
push di
les di,[src]
mov di,[xpos]
mov ax,[ypos]
xchg ah,al ;ax=256*y
add di,ax ;di=256*y+x
shr ax,2 ;ax=64*y
add di,ax ;di=320*y+x offset to di
mov al,[es:di] ;color to al
pop di
pop bp
ret
ENDP _getpixel


;************************************************************************
;* void show32spr( int xpos, int ypos, char far*sprite, char far*dest); *
;* Register: ax,bx,cx,dx *
;* width of dest must be 320 bytes *
;* draws sprite at dest at (xpos,ypos) *
;************************************************************************
PROC _show32spr NEAR

ARG xpos:Word, ypos:Word, sprite:Dword, dest:Dword

push bp
mov bp,sp
push ds
push si
push di
lds si,[sprite] ;ds:si = sprite
les di,[dest] ;es:di = destination
mov di,[xpos]
mov ax,[ypos]
xchg ah,al ;ax=256*y
add di,ax ;di=256*y+x
shr ax,2 ;ax=64*y
add di,ax ;di=320*y+x offset to di

;get the proportions from the head
lodsw ;height to ax
mov dx,ax ;height to dx
or ax,ax
jz @@endspr ;if height 0 draw nothing
lodsw ;width to ax
mov bx,320 ;bx = pix/row
sub bx,ax ;bx = incr (# of bytes to next row)
shr ax,2 ;ax = width/4

;draw the sprite
@@Loop32:
mov cx,ax ;# of Dword per row to cx
rep movsd ;move one spriterow from ds:si to es:di
add di,bx ;di:=next row
dec dx
jnz @@Loop32 ;loopa through all rows
@@endspr:
pop di
pop si
pop ds
pop bp
ret
ENDP _show32spr

;************************************************************************
;* void showtrans(int xpos, int ypos, char far*sprite, char far*dest); *
;* Register: ax,bx,cx,dx *
;* width on dest must be 320 bytes *
;* shows sprite with color 0 transparent, slower than show32spr *
;************************************************************************
PROC _showtrans NEAR
ARG xpos:Word, ypos:Word, sprite:Dword, dest:Dword
push bp
mov bp,sp
push ds
push si
push di
lds si,[sprite] ;ds:si = sprite
les di,[dest] ;es:di = destination
mov di,[xpos]
mov ax,[ypos]
xchg ah,al ;ax=256*y
add di,ax ;di=256*y+x
shr ax,2 ;ax=64*y
add di,ax ;di=320*y+x offset to di
lodsw ;height to ax
mov dx,ax ;height to dx
or ax,ax
jz @@error ;if height 0 draw nothing
lodsw ;width to ax
mov bx,320 ;bx = pix/screenrow
sub bx,ax ;bx = incr (bytes to next row)
shl eax,16 ;save width in high word of eax
;draw sprite
@@Lp1: shr eax,16 ;width => ax
mov cx,ax ;bytes/row => cx
shl eax,16 ;save in high word
@@Lp2: mov al,[ds:si] ;load one byte
or al,al
jz @@itsz ;if 0 jump to itsz
mov [es:di],al ;if not 0 draw it
@@itsz: inc di
inc si
loop @@Lp2 ;to next pixel

add di,bx ;di:=next row
dec dx
jnz @@Lp1 ;loop through all rows
@@error:
pop di
pop si
pop ds
pop bp
ret
ENDP _showtrans


;************************************************************************
;* void erase32bit(int x, int y, int width, int height,char far*dest); *
;* Register: eax, ebx, cx, dx, *
;* erases (draws color 0) on a rectangle in dest *
;************************************************************************
PROC _erase32bit NEAR

ARG xpos:Word, ypos:Word, width:Word, height:Word, dest:Dword

push bp
mov bp,sp
push di
les di,[dest]
mov di,[xpos]
mov ax,[ypos]
xchg ah,al ;ax=256*y
add di,ax ;di=256*y+x
shr ax,2 ;ax=64*y
add di,ax ;di=320*y+x
mov ax,[width]
mov dx,[height]
mov bx,320
sub bx,ax
shr ax,2
shl ebx,16
mov bx,ax ;ebx=offset to next row, width in Dword
xor eax,eax ;eax = 0
@@sudda:
mov cx,bx ;Dword/row to cx
rep stosd ;erase one row at es:di
ror ebx,16 ;rotate offset to bx
add di,bx ;di = next row
rol ebx,16 ;rotate back to high word
dec dx
jnz @@sudda ;loopa through all rows
pop di
pop bp
ret
ENDP _erase32bit

;************************************************************************
;* void getback32(int xpos, int ypos, int width, int height, *
;* char far*source, char far*spr); *
;* Register: ax, bx, cx, dx, *
;************************************************************************
; make a sprite out of source at x,y with width width and height height
PROC _getback32 NEAR
ARG xpos:Word, ypos:Word, width:Word, height:Word, source:Dword, sprite:Dword
push bp
mov bp,sp
push di
push si
push ds
lds si,[source] ;ds:si => source
les di,[sprite] ;es:di => sprite
mov si,[xpos]
mov ax,[ypos]
xchg ah,al
add si,ax
shr ax,2
add si,ax ;si=320*y+x
mov ax,[height] ;save height and width
stosw ;first height
mov dx,ax ;dx height counter
mov ax,[width]
stosw ;then width
mov bx,320
sub bx,ax ;bx=bytes to next row
shr ax,2 ;ax = width/4
@@loop: mov cx,ax ;number of Dwords to cx per row
rep movsd ;move one row to sprite
add si,bx ;next row
dec dx ;was it the last?
jnz @@loop ;not, one more
pop ds
pop si
pop di
pop bp
ret
ENDP _getback32

;****************************************
;* void wtsync(void); Reg: inga *
;* waits for vertical retrace *
;****************************************
PROC _wtsync NEAR
push ax
push dx
mov dx,3DAh
@@wt1: in al,dx
test al,8
jne @@wt1
@@wt2: in al,dx
test al,8
je @@wt2
pop dx
pop ax
ret
ENDP _wtsync

;**************************************************
;*** void blackpal(void) REG: al, cx, dx ***
;*** sets all colors to black ***
;**************************************************
PROC _blackpal NEAR
xor al,al
mov dx,3c8h
out dx,al
inc dx
mov cx,256*3
@@lp: out dx,al
loop @@lp
ret
ENDP _blackpal

;************************************************************
;*** void setpal(unsigned char far*pal) REG: none *****
;*** sets the active palette to pal *****
;************************************************************
PROC _setpal NEAR
ARG pal:Dword
push bp
mov bp,sp
push ax
push cx
push dx
push ds
push si
lds si,[pal]
xor al,al
mov dx,3c8h
out dx,al
inc dx
mov cx,256*3
rep outsb
pop si
pop ds
pop dx
pop cx
pop ax
pop bp
ret
ENDP _setpal

;**************************************************************************
;*** char getch(void) Reg:ax ***
;*** waits for keypress, returns the ascii-code of the pressed char ***
;**************************************************************************
PROC _getch NEAR
mov ah,0 ;stops prog & waits for keypress
int 16h ;ascii-code in al, scan-code in ah
ret
ENDP _getch


;*** char kbhit(void) Reg:ax ***
;checks for keypress, no? return 0
;if keypress return the ascii-code of the pressed key
PROC _kbhit NEAR
mov ah,1
int 16h
jnz @@99 ;if kb is hit jump
xor ax,ax ;else return 0
jmp @@88
@@99: mov ah,0
int 16h ;remove keypress from keyboredbuffer
@@88: ret
ENDP _kbhit


;*** void setmode(int mode) ***
PROC _setmode NEAR
ARG mode:Word
push bp
mov bp,sp
mov ax,[mode]
int 10h
pop bp
ret
ENDP _setmode


;*** void flip64k(char far*source, char far*dest) ***
;*** register: cx ***
;copies 64000 bytes from source to dest
PROC _flip64k NEAR
ARG source:Dword, dest:Dword
push bp
mov bp,sp
push ds
push si
push di
lds si,[source]
les di,[dest]
mov cx,16000 ;64000/4
rep movsd
pop di
pop si
pop ds
pop bp
ret
ENDP _flip64k

;*** void disflip64k(char far*source, char far*dest) ***
;*** register: cx,al,dx ***
;Makes a dissolveflip from source to dist
;Change the speed of the flip by changing the constant 900 (2 places)
PROC _disflip64k NEAR
ARG source:Dword, dest:Dword
push bp
mov bp,sp
push ds
push si
push di
lds si,[source]
les di,[dest]
mov cx,0ffffh
mov di,1
mov dx,900
@@dis1: mov al,[ds:di] ;move 1 point from ds:di to es:di
mov [es:di],al
shr di,1
jnc @@noc ;di is calculated with an algorithm from
xor di,0b400h ;Graphic Gems I (Digital Dissolve effect)
@@noc: dec dx
jnz @@ndel
mov dx,900 ;every 900:th pixel make a delay by
call _wtsync ;calling wtsync
@@ndel: loop @@dis1
pop di
pop si
pop ds
pop bp
ret
ENDP _disflip64k


;*** void disflipblack(char far*source, char far*dest) ***
;*** register: cx,al,dx ***
;Makes a dissolveflip from source to black areas of dist ***
;Change the speed of the flip by changing the constant 900 (2 places)
PROC _disflipblack NEAR
ARG source:Dword, dest:Dword
push bp
mov bp,sp
push ds
push si
push di
lds si,[source]
les di,[dest]
mov cx,0ffffh
mov di,1
mov dx,900
@@dis1: mov al,[es:di] ;investigate the point that shall be flipped
cmp al,0 ;if the screen is not black there
jnz @@skip ;skip it (don't draw the point)
mov al,[ds:di] ;move 1 point from ds:di to es:di
mov [es:di],al
@@skip: shr di,1
jnc @@noc ;di is calculated with an algorithm from
xor di,0b400h ;Graphic Gems I (Digital Dissolve effect)
@@noc: dec dx
jnz @@ndel
mov dx,900 ;every 900:th pixel make a delay by
call _wtsync ;calling wtsync
@@ndel: loop @@dis1
pop di
pop si
pop ds
pop bp
ret
ENDP _disflipblack


;*** char far* malloc(unsigned int bytes) ***
;allocates bytes bytes, returnes far char pointer to memoryblock
;ex: char far* virt;
; virt=malloc(64000);
;if out of memory, return 0
PROC _malloc NEAR
ARG bytes:Word
push bp
mov bp,sp
mov bx,[bytes]
shr bx,4
inc bx
mov ah,48h ;funk 48 allocates ah number of paragraphs
int 21h ;1 paragraph = 16 bytes, segment in ax
mov dx,ax ;move the segment to dx
jnc @@nerr ;if no carry, no error
xor dx,dx ;an error occured . . .
@@nerr: xor ax,ax ;offset always 0
pop bp
ret
ENDP _malloc


;*** int free(char far* block) ***
;frees memory allocated with malloc (routine above)
;send the pointer as parameter ex: free(virt);
PROC _free NEAR
ARG block:Dword
push bp
mov bp,sp
les ax,[block]
mov ah,49h
int 21h
jnc @@nerr
xor ax,ax ;an error occured . . .
@@nerr: pop bp
ret
ENDP _free

END ;Just END no label

Abe's Demoschool: Part IV screenshot 1
Pin it
Abe's Demoschool: Part IV screenshot 1
Abe's Demoschool: Part IV screenshot 2
Pin it
Abe's Demoschool: Part IV screenshot 2

DEMO4.H

extern int loadpalett(char far*name,char far*pal); 
extern int loadsprite(char far*name,char far*sprite);
extern int showpic(int x,int y,char far*name,char far*dest);
extern void show32spr(int x,int y,char far*sprite,char far*dest);
extern void showtrans(int x,int y,char far*sprite,char far*dest);
extern void runnyspr(int x,int y,char far*sprite,char far*dest);
extern void erase32bit(int x, int y, int width, int height, char far*dest);
extern void getback32(int x,int y,int width,int height,char far*source,char far*dest);
extern char getch(void);
extern void setmode(int mode);
extern void setpal(char far*pal);
extern void flip64k(char far*source, char far*dest);
extern void disflip64k(char far*source, char far*dest);
extern void disflipblack(char far*source, char far*dest);
extern char far*malloc(unsigned int nr_of_bytes);
extern int free(char far*memoryblock);
extern void wtsync(void);
extern char kbhit(void);
extern void RotatePoints(char ax,char ay,char az,int antal);
extern void qsort3d(int lb, int ub);
extern void outstr(char far*str);
extern char far* screenadr(void);


DEMO4.C

/**********************************************/ 
/* Sourcecode for part IV in Abe's Demoschool */
/* */
/* 3D rotation with sprites */
/* */
/* 1996-02-07 Sir Abe */
/* mail: dat94avi@bilbo.mdh.se */
/* s-mail: Albert Veli */
/* Spisringsg. 9 */
/* 724 76 VÑsterÜs */
/* SWEDEN */
/**********************************************/

#include "demo4.h" /* contains all extern declarations */

#define NO_OF_PTS 8 /* 8 points, the corners of the cube */
#define SZ 40 /* change SZ to change the size of the cube */

/* GLOBAL VARIABLES AND ARRAYS */
int bx[NO_OF_PTS]={-SZ,SZ,SZ,-SZ,-SZ,SZ,SZ,-SZ}; /* The base object */
int by[NO_OF_PTS]={SZ,SZ,-SZ,-SZ,SZ,SZ,-SZ,-SZ}; /* Now It's a cube */
int bz[NO_OF_PTS]={SZ,SZ,SZ,SZ,-SZ,-SZ,-SZ,-SZ}; /* but it's easy to change */

int rx[NO_OF_PTS],ry[NO_OF_PTS],rz[NO_OF_PTS]; /* these arrays will be used */
int sx[NO_OF_PTS],sy[NO_OF_PTS]; /* by rotatepoints */
int addx=0,addy=0,addz=0; /* these variables moves the object */


/* printbutt print's some text on the screen using outstr, which uses */
/* int 21 funtion 9 to print. The string must end with a 0 or a $ */
/* C automatically puts a 0 at the end of a "text" inside " " signs */
/* \n\r means new line and go back to the beginning of row */
/* \ at the end of the rows means that the string continues on the next row */
/* just make sure text is big enough to contain the all the text */
void printbutt(void)
{
char text[1000]="\n\r\
Basic 3d-rotation, 8 sprites on the corners of a rotating cube\n\r\n\r\
Controls:\n\r\n\r\
Axis: x-rotation y-rotation z-rotation\n\r\
Action: dec inc dec inc dec inc\n\r\
⁄ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒø\n\r\
Button:≥ Z X C V B N ≥\n\r\
¿ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒŸ\n\r\n\r\
Move: left right up down out in\n\r\
⁄ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒø\n\r\
Button:≥ Q W E R T Y ≥\n\r\
¿ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒŸ\n\r\n\r\
Stop rotation: Return\n\r\
Exit: Escape\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\
Press a button to continue. . ."
;
outstr(text); /* print the string text, defined above */
getch(); /* wait for a keypress */
}

void printerr(void) /* prints an error text if there isn't enough memory */
{
char text[200]="\n\r\n\r\
Couldn't allocate enough memory for the virtual screen :-(\n\r\
Reboot your computer and try again\n\r"
;
outstr(text);
}

void printabe(void)
{
char text[300]="\
‹ ‹‹‹‹‹ ‹\n\r\
€€€ fi€flflfl€› ‹€fl€‹\n\r\
fi€ €› fi € € fi›\n\r\
€› fi€ fi€‹‹‹€› fi€‹\n\r\
fi€ €›fi€flflfl€€ €fl\n\r\
fi€€€€€›fi €› € fi›\n\r\
fi€ €›fi€‹‹‹€€ fi€‹€fl \n\r\
fl fl flflflflfl fl \n\r\
⁄ƒƒø ¬\n\r\
≥ ƒø ≥\n\r\
Wishes: ¿ƒƒŸood ¿ƒƒŸuck\n\r"
;
outstr(text);
}

void main(void)
{
int i;
int per;
unsigned char ax,ay,az,incx,incy,incz,pal[256*3];
char far*scr;
char far*virt;
char sprite[4+20*20],ch;
int width,height;

virt=malloc(64000); /* this is exactly like in part III, remember? */
if(virt!=0)
{
printbutt(); /* print the instructions on the screen */
scr=screenadr(); /* screenadr returns 0a000:0000, it's in graph.asm */
incx=1; /* incx,incy,incz is the rotation speed in each direction */
incy=-1; /* it can be changed with keys z to n on the keyboard */
incz=0;
ax=0; /* ax,ay,az are the angles the cube will be rotated with */
ay=0;
az=0;

setmode(0x0013);
flip64k(scr,virt); /* clear virt */
loadsprite("blueb.spr",sprite); /* sprite stuff, similar to part III */
width=sprite[2]+sprite[3]*256;
height=sprite[0]+sprite[1]*256;
loadpalett("blueb.pal",pal);
setpal(pal);

RotatePoints(0,0,0,NO_OF_PTS);/* rotate the object so sx,sy get's initialized */
for(i=4;i<8;i++) runnyspr(sx[i],sy[i],sprite,scr); /* the runny effect from part III */


/* THE MAIN LOOP */
do{
/* first erase the old sprites */
for(i=0;i<NO_OF_PTS;i++) erase32bit(sx[i],sy[i],width,height,virt);

RotatePoints(ax,ay,az,NO_OF_PTS); /* rotate, calculate new coordinates */

qsort3d(0,NO_OF_PTS - 1); /* sort sx,sy with respect to rz */

/* draw the sprites to virt in back to front order (painters algorithm) */
for(i=NO_OF_PTS-1;i>=0;i--) showtrans(sx[i],sy[i],sprite,virt);
wtsync(); /* wait for the electron beam, (skip this row if it's slow) */
flip64k(virt,scr); /* and flip virt over to the visible screen */

ax+=incx; /* change the angles */
ay+=incy;
az+=incz;

ch=kbhit(); /* check for keypress */
if(ch!=0) /* if there's been a keypress */
{
switch(ch) /* Check which key has been pressed and interact */
{
case 'z': /* z and Z decreases the x-rotation speed */
case 'Z': incx--;
break;
case 'x': /* x and X increases the x-rotation speed */
case 'X': incx++;
break;
case 'c': /* c and C decreases the y-rotation speed */
case 'C': incy--;
break;
case 'v': /* v and V increases the y-rotation speed */
case 'V': incy++;
break;
case 'b': /* b and B decreases the z-rotation speed */
case 'B': incz--;
break;
case 'n': /* n and N increases the z-rotation speed */
case 'N': incz++;
break;
case '\r': incx=0;incy=0;incz=0;ax=0;ay=0;az=0; /* return stops rotation */
break;
case 'q': /* q, Q, w & W changes the x-position of the object */
case 'Q': addx--;
break;
case 'w':
case 'W': addx++;
break;
case 'e': /* e, E, r & R changes the y-position of the object */
case 'E': addy--;
break;
case 'r':
case 'R': addy++;
break;
case 't': /* t and T decreases addz, makes the object come closer */
case 'T': if(addz>0) addz-=5;
break;
case 'y': /* y and Y increases addz, makes the object go away, get smaller*/
case 'Y': addz+=5;
break;
}
}
}while(ch!='\x1b');
free(virt);
}
else printerr(); /* if there wasn't enough memory, print the error text */
setmode(3); /* get back into textmode */
printabe(); /* print a little message */
}

/***************************************************************/
/* This is an attempt to make this file 6 666 bytes muahaha */
/***************************************************************/


D4MORF.C

rotation and morphing between the base-objects demo

/**********************************************/ 
/* Sourcecode for part IV in Abe's Demoschool */
/* */
/* 3D rotation with sprites & morfing */
/* on the base objects. */
/* Try stopping the rotation with Return */
/* And check the morph with keys 1,2,3 */
/* */
/* 1996-02-07 Sir Abe */
/* mail: dat94avi@bilbo.mdh.se */
/* s-mail: Albert Veli */
/* Spisringsg. 9 */
/* 724 76 VÑsterÜs */
/* SWEDEN */
/**********************************************/

#include "demo4.h" /* contains all extern declarations */

#define NO_OF_PTS 9 /* 9 points in each object */
#define NO_OF_OBJ 3 /* 3 different base objects to morph between */
#define SIZE 10

/* GLOBAL VARIABLES AND ARRAYS */
int bx[NO_OF_PTS]; /* the morphed base object will be copied here */
int by[NO_OF_PTS]; /* in the beginning of the main loop */
int bz[NO_OF_PTS];

int rx[NO_OF_PTS],ry[NO_OF_PTS],rz[NO_OF_PTS]; /* these arrays will be used */
int sx[NO_OF_PTS],sy[NO_OF_PTS]; /* by rotatepoints */
int addx=0,addy=0,addz=0; /* these variables moves the object */

/* printbutt print's some text on the screen using outstr, which uses */
/* int 21 funtion 9 to print. The string must end with a 0 or a $ */
/* C automatically puts a 0 at the end of a "text" inside " " signs */
/* \n\r means new line and go back to the beginning of row */
/* \ at the end of the rows means that the string continues on the next row */
/* just make sure text is big enough to contain the all the text */
void printbutt(void)
{
char text[1000]="\n\r\
3D-rotation with morphing between the 3 base objects\n\r\n\r\
Controls:\n\r\n\r\
Morph to: object 1 object 2 object 3\n\r\
⁄ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒø\n\r\
Button:≥ 1 2 3 ≥\n\r\
¿ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒŸ\n\r\n\r\
Axis: x-rotation y-rotation z-rotation\n\r\
Action: dec inc dec inc dec inc\n\r\
⁄ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒø\n\r\
Button:≥ Z X C V B N ≥\n\r\
¿ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒŸ\n\r\n\r\
Move: left right up down out in\n\r\
⁄ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒø\n\r\
Button:≥ Q W E R T Y ≥\n\r\
¿ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒŸ\n\r\n\r\
Stop rotation: Return\n\r\
Exit: Escape\n\r\n\r\n\r\
Press a button to continue. . ."
;
outstr(text); /* print the string text, defined above */
getch(); /* wait for a keypress */
}

void printerr(void) /* prints an error text if there isn't enough memory */
{
char text[200]="\n\r\n\r\
Couldn't allocate enough memory for the virtual screen :-(\n\r\
Reboot your computer and try again\n\r"
;
outstr(text);
}

void printabe(void)
{
char text[300]="\
‹ ‹‹‹‹‹ ‹\n\r\
€€€ fi€flflfl€› ‹€fl€‹\n\r\
fi€ €› fi € € fi›\n\r\
€› fi€ fi€‹‹‹€› fi€‹\n\r\
fi€ €›fi€flflfl€€ €fl\n\r\
fi€€€€€›fi €› € fi›\n\r\
fi€ €›fi€‹‹‹€€ fi€‹€fl \n\r\
fl fl flflflflfl fl \n\r\
⁄ƒƒø ¬\n\r\
≥ ƒø ≥\n\r\
Wishes: ¿ƒƒŸood ¿ƒƒŸuck\n\r"
;
outstr(text);
}

void main(void)
{
int i;
int per;
unsigned char ax,ay,az,incx,incy,incz,pal[256*3];
char far*scr;
char far*virt;
char sprite[4+20*20],ch;
int width,height,curr_obj;

/* Base coordinats of the objects */
/* change these to something cool */
/* right now it's the letters A, b and E (if you didn't notice that) */
int xt[NO_OF_OBJ][NO_OF_PTS]={{0,-4,4,-8,-3,3,8,-8,8},{-2,-2,-2,-2,-2,0,2,2,0},{3,-1,-4,-3,-2,-3,-4,-1,3}};
int yt[NO_OF_OBJ][NO_OF_PTS]={{-4,-2,-2,1,1,1,1,5,5},{-5,-3,-1,1,3,3,2,0,-1},{-6,-5,-4,-2,0,2,4,5,6}};
int zt[NO_OF_OBJ][NO_OF_PTS]={0};

float mx[NO_OF_PTS],my[NO_OF_PTS],mz[NO_OF_PTS]; /* the morphed coordinates */

virt=malloc(64000); /* this is exactly like in part III, remember? */
if(virt!=0)
{
printbutt(); /* print the instructions on the screen */
scr=screenadr(); /* screenadr returns 0a000:0000, it's in graph.asm */
incx=1; /* incx,incy,incz is the rotation speed in each direction */
incy=0; /* it can be changed with keys z to n on the keyboard */
incz=0;
ax=0; /* ax,ay,az are the angles the cube will be rotated with */
ay=0;
az=0;

setmode(0x0013);
flip64k(scr,virt); /* clear virt */
loadsprite("blueb.spr",sprite); /* sprite stuff, similar to part III */
width=sprite[2]+sprite[3]*256;
height=sprite[0]+sprite[1]*256;
loadpalett("blueb.pal",pal);
setpal(pal);

curr_obj=0; /* start with the first object */
for (i=0;i<NO_OF_PTS;i++)
{
xt[0][i]=xt[0][i]*SIZE*0.6; /* increase the size of the base objects */
xt[1][i]=xt[1][i]*SIZE*1.6;/* you could insert the final size into xt,yt,yz */
xt[2][i]=xt[2][i]*SIZE; /* in the beginning and skip these lines */
yt[0][i]=yt[0][i]*SIZE; /* but now it's easier to change the size */
yt[1][i]=yt[1][i]*SIZE*1.3; /* just change the #define SIZE */
yt[2][i]=yt[2][i]*SIZE;
//zt[0][i]=zt[0][i]*SIZE;
//zt[1][i]=zt[1][i]*SIZE;
//zt[2][i]=zt[2][i]*SIZE;
mx[i]=0; /* initialize mx,my & mz */
my[i]=0;
mz[i]=0;
sx[i]=0; /* initialize sx,sy */
sy[i]=0;
}

runnyspr(160,100,sprite,scr); /* the runny effect from part III */


/* THE MAIN LOOP */
do{
/* morph away */
for(i=0;i<NO_OF_PTS;i++)
{
mx[i]+=((xt[curr_obj][i]-mx[i])*0.05); /* add distance/20 */
my[i]+=((yt[curr_obj][i]-my[i])*0.05);
mz[i]+=((zt[curr_obj][i]-mz[i])*0.05);
bx[i]=mx[i];
by[i]=my[i];
bz[i]=mz[i];
}

/* erase the old sprites */
for(i=0;i<NO_OF_PTS;i++) erase32bit(sx[i],sy[i],width,height,virt);
RotatePoints(ax,ay,az,NO_OF_PTS); /* rotate, calculate new coordinates */

qsort3d(0,NO_OF_PTS - 1); /* sort sx,sy with respect to rz */

/* draw the sprites to virt in back to front order (painters algorithm) */
for(i=NO_OF_PTS-1;i>=0;i--) showtrans(sx[i],sy[i],sprite,virt);

/* wait for the electron beam */
wtsync(); /* (skip this row if you have a slower computer than 486 50Mhz) */
flip64k(virt,scr); /* and flip virt over to the visible screen */

ax+=incx; /* change the angles */
ay+=incy;
az+=incz;

ch=kbhit(); /* check for keypress */
if(ch!=0) /* if there's been a keypress */
{
switch(ch) /* Check which key has been pressed and interact */
{
case 'z': /* z and Z decreases the x-rotation speed */
case 'Z': incx--;
break;
case 'x': /* x and X increases the x-rotation speed */
case 'X': incx++;
break;
case 'c': /* c and C decreases the y-rotation speed */
case 'C': incy--;
break;
case 'v': /* v and V increases the y-rotation speed */
case 'V': incy++;
break;
case 'b': /* b and B decreases the z-rotation speed */
case 'B': incz--;
break;
case 'n': /* n and N increases the z-rotation speed */
case 'N': incz++;
break;
case '\r': incx=0;incy=0;incz=0;ax=0;ay=0;az=0; /* return stops rotation */
break;
case 'q': /* q and Q, decrease x-position of all points */
case 'Q': addx--;
break;
case 'w': /* w and W, increase x-position of all points */
case 'W': addx++;
break;
case 'e': /* e, r change y-position */
case 'E': addy--;
break;
case 'r':
case 'R': addy++;
break;
case 't': /* t and T decreases addz, makes the object come closer */
case 'T': if(addz>0) addz-=5;
break;
case 'y': /* y and Y increases addz, makes the object go away, get smaller*/
case 'Y': addz+=5;
break;
case '1': curr_obj=0;
break;
case '2': curr_obj=1;
break;
case '3': curr_obj=2;
break;
}
}
}while(ch!='\x1b');
free(virt);
}
else printerr(); /* if there wasn't enough memory, print the error text */
setmode(3); /* get back into textmode */
printabe(); /* print a little message */
}


BUILD.BAT

bcc d4morf.c rot.asm graph.asm

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT