; Fixed point routines.
; Tested with TASM 3.0.

USE386          equ     1       ;1 for 386-specific opcodes, 0 for
																; 8088 opcodes
MUL_ROUNDING_ON equ     0       ;1 for rounding on multiplies,
																; 0 for no rounding. Not rounding is faster,
																; rounding is more accurate and generally a
																; good idea
DIV_ROUNDING_ON equ     0       ;1 for rounding on divides,
																; 0 for no rounding. Not rounding is faster,
																; rounding is more accurate, but because
																; division is only performed to project to
																; the screen, rounding quotients generally
																; isn't necessary
ALIGNMENT       equ     2

				.model large
				.386
				.code

;=====================================================================
; Multiplies two fixed-point values together.
; C near-callable as:
;       Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2);
FMparms struc
				dd
				dw      ?        ;return address & pushed BP

M1      dd      ?
M2      dd      ?
FMparms ends
				align   ALIGNMENT
				public  _FixedMul
_FixedMul       proc far
				push    bp
				mov     bp,sp

if USE386

				mov     eax,[bp+M1]
				imul    dword ptr [bp+M2] ;multiply
if MUL_ROUNDING_ON
				add     eax,8000h       ;round by adding 2^(-17)
				adc     edx,0           ;whole part of result is in DX
endif ;MUL_ROUNDING_ON
				shr     eax,16          ;put the fractional part in AX

else    ;!USE386

																;do four partial products and
																; add them together, accumulating
																; the result in CX:BX
				push    si              ;preserve C register variables
				push    di
																;figure out signs, so we can use
																; unsigned multiplies
				sub     cx,cx           ;assume both operands positive
				mov     ax,word ptr [bp+M1+2]
				mov     si,word ptr [bp+M1]
				and     ax,ax           ;first operand negative?
				jns     CheckSecondOperand ;no
				neg     ax              ;yes, so negate first operand
				neg     si
				sbb     ax,0
				inc     cx              ;mark that first operand is negative
CheckSecondOperand:
				mov     bx,word ptr [bp+M2+2]
				mov     di,word ptr [bp+M2]
				and     bx,bx           ;second operand negative?
				jns     SaveSignStatus  ;no
				neg     bx              ;yes, so negate second operand
				neg     di
				sbb     bx,0
				xor     cx,1            ;mark that second operand is negative
SaveSignStatus:
				push    cx              ;remember sign of result; 1 if result
																; negative, 0 if result nonnegative
				push    ax              ;remember high word of M1
				mul     bx              ;high word M1 times high word M2
				mov     cx,ax           ;accumulate result in CX:BX (BX not used
																; until next operation, however)
																;assume no overflow into DX
				mov     ax,si           ;low word M1 times high word M2
				mul     bx
				mov     bx,ax
				add     cx,dx           ;accumulate result in CX:BX
				pop     ax              ;retrieve high word of M1
				mul     di              ;high word M1 times low word M2
				add     bx,ax
				adc     cx,dx           ;accumulate result in CX:BX
				mov     ax,si           ;low word M1 times low word M2
				mul     di
if MUL_ROUNDING_ON
				add     ax,8000h        ;round by adding 2^(-17)
				adc     bx,dx
else ;!MUL_ROUNDING_ON
				add     bx,dx           ;don't round
endif ;MUL_ROUNDING_ON
				adc     cx,0            ;accumulate result in CX:BX
				mov     dx,cx
				mov     ax,bx
				pop     cx
				and     cx,cx           ;is the result negative?
				jz      FixedMulDone    ;no, we're all set
				neg     dx              ;yes, so negate DX:AX
				neg     ax
				sbb     dx,0
FixedMulDone:

				pop     di              ;restore C register variables
				pop     si

endif   ;USE386

				pop     bp
				ret
_FixedMul       endp

;=====================================================================
; Divides one fixed-point value by another.
; C near-callable as:
;       Fixedpoint FixedDiv(Fixedpoint Dividend, Fixedpoint Divisor);
FDparms struc
				dd
				dw      ?        ;return address & pushed BP
Dividend dd     ?
Divisor  dd     ?
FDparms ends
				align   ALIGNMENT
				public  _FixedDiv
_FixedDiv       proc far
				push    bp
				mov     bp,sp

if USE386

if DIV_ROUNDING_ON
				sub     cx,cx           ;assume positive result
				mov     eax,[bp+Dividend]
				and     eax,eax         ;positive dividend?
				jns     FDP1            ;yes
				inc     cx              ;mark it's a negative dividend
				neg     eax             ;make the dividend positive
FDP1:   sub     edx,edx         ;make it a 64-bit dividend, then shift
																; left 16 bits so that result will be
																; in EAX
				rol     eax,16          ;put fractional part of dividend in
																; high word of EAX
				mov     dx,ax           ;put whole part of dividend in DX
				sub     ax,ax           ;clear low word of EAX
				mov     ebx,dword ptr [bp+Divisor]
				and     ebx,ebx         ;positive divisor?
				jns     FDP2            ;yes
				dec     cx              ;mark it's a negative divisor
				neg     ebx             ;make divisor positive
FDP2:   div     ebx             ;divide
				shr     ebx,1           ;divisor/2, minus 1 if the divisor is
				adc     ebx,0           ; even
				dec     ebx
				cmp     ebx,edx         ;set Carry if the remainder is at least
				adc     eax,0           ; half as large as the divisor, then
																; use that to round up if necessary
				and     cx,cx           ;should the result be made negative?
				jz      FDP3            ;no
				neg     eax             ;yes, negate it
FDP3:
else ;!DIV_ROUNDING_ON
				mov     edx,[bp+Dividend]
				sub     eax,eax
				shrd    eax,edx,16      ;position so that result ends up
				sar     edx,16          ; in EAX
				idiv    dword ptr [bp+Divisor]
endif ;DIV_ROUNDING_ON
				shld    edx,eax,16      ;whole part of result in DX;
																; fractional part is already in AX

else ;!USE386

;NOTE!!! Non-386 division uses a 32-bit dividend but only the upper 16 bits
; of the divisor; in other words, only the integer part of the divisor is
; used. This is done so that the division can be accomplished with two fast
; hardware divides instead of a slow software implementation, and is (in my
; opinion) acceptable because division is only used to project points to the
; screen (normally, the divisor is a Z coordinate), so there's no cumulative
; error, although there will be some error in pixel placement (the magnitude
; of the error is less the farther away from the Z=0 plane objects are). This
; is *not* a general-purpose divide, though; if the divisor is less than 1,
; for instance, a divide-by-zero error will result! For this reason, non-386
; projection can't be performed for points closer to the viewpoint than Z=1.

																;figure out signs, so we can use
																; unsigned divisions
				sub     cx,cx           ;assume both operands positive
				mov     ax,word ptr [bp+Dividend+2]
				and     ax,ax           ;first operand negative?
				jns     CheckSecondOperandD ;no
				neg     ax              ;yes, so negate first operand
				neg     word ptr [bp+Dividend]
				sbb     ax,0
				inc     cx              ;mark that first operand is negative
CheckSecondOperandD:
				mov     bx,word ptr [bp+Divisor+2]
				and     bx,bx           ;second operand negative?
				jns     SaveSignStatusD ;no
				neg     bx              ;yes, so negate second operand
				neg     word ptr [bp+Divisor]
				sbb     bx,0
				xor     cx,1            ;mark that second operand is negative
SaveSignStatusD:
				push    cx              ;remember sign of result; 1 if result
																; negative, 0 if result nonnegative
				sub     dx,dx           ;put Dividend+2 (integer part) in DX:AX
				div     bx              ;first half of 32/16 division, integer part
																; divided by integer part
				mov     cx,ax           ;set aside integer part of result
				mov     ax,word ptr [bp+Dividend] ;concatenate the fractional part of
																; the dividend to the remainder (fractional
																; part) of the result from dividing the
																; integer part of the dividend
				div     bx              ;second half of 32/16 division

if DIV_ROUNDING_ON EQ 0
				shr     bx,1            ;divisor/2, minus 1 if the divisor is
				adc     bx,0            ; even
				dec     bx
				cmp     bx,dx           ;set Carry if the remainder is at least
				adc     ax,0            ; half as large as the divisor, then
				adc     cx,0            ; use that to round up if necessary
endif ;DIV_ROUNDING_ON

				mov     dx,cx           ;absolute value of result in DX:AX
				pop     cx
				and     cx,cx           ;is the result negative?
				jz      FixedDivDone    ;no, we're all set
				neg     dx              ;yes, so negate DX:AX
				neg     ax
				sbb     dx,0
FixedDivDone:

endif ;USE386

				pop     bp
				ret
_FixedDiv       endp

				end
