; Fixed point routines.
; Tested with TASM 4.0.

USE386          equ     1       ;1 for 386-specific opcodes, 0 for
                                ; 8088 opcodes
MUL_ROUNDING_ON equ     1       ;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 small
        .386
        .code

;=====================================================================
; Multiplies two fixed-point values together.
; C near-callable as:
;       Fixedpoint FixedMul(Fixedpoint M1, Fixedpoint M2);
FMparms struc
        dw      2 dup(?)        ;return address & pushed BP
M1      dd      ?
M2      dd      ?
FMparms ends
        align   ALIGNMENT
        public  _FixedMul
_FixedMul       proc    near
        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     short 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     short 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      short 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
        dw      2 dup(?)        ;return address & pushed BP
Dividend dd     ?
Divisor  dd     ?
FDparms ends
        align   ALIGNMENT
        public  _FixedDiv
_FixedDiv       proc    near
        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     short 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     short 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      short 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     short 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     short 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      short 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

;=====================================================================
; Returns the sine and cosine of an angle.
; C near-callable as:
;       void CosSin(TAngle Angle, Fixedpoint *Cos, Fixedpoint *);

        align   ALIGNMENT
CosTable label dword
        include costable.inc

SCparms struc
        dw      2 dup(?)        ;return address & pushed BP
Angle   dw      ?               ;angle to calculate sine & cosine for
Cos     dw      ?               ;pointer to cos destination
Sin     dw      ?               ;pointer to sin destination
SCparms ends

        align   ALIGNMENT
        public _CosSin
_CosSin proc    near
        push    bp              ;preserve stack frame
        mov     bp,sp           ;set up local stack frame

if USE386

        mov     bx,[bp].Angle
        and     bx,bx           ;make sure angle's between 0 and 2*pi
        jns     short CheckInRange
MakePos:                        ;less than 0, so make it positive
        add     bx,360*10
        js      short MakePos
        jmp     short CheckInRange

        align   ALIGNMENT
MakeInRange:                    ;make sure angle is no more than 2*pi
        sub     bx,360*10
CheckInRange:
        cmp     bx,360*10
        jg      short MakeInRange

        cmp     bx,180*10       ;figure out which quadrant
        ja      short BottomHalf ;quadrant 2 or 3
        cmp     bx,90*10        ;quadrant 0 or 1
        ja      short Quadrant1
                                ;quadrant 0
        shl     bx,2
        mov     eax,CosTable[bx] ;look up sine
        neg     bx              ;sin(Angle) = cos(90-Angle)
        mov     edx,CosTable[bx+90*10*4] ;look up cosine
        jmp     short CSDone

        align   ALIGNMENT
Quadrant1:
        neg     bx
        add     bx,180*10       ;convert to angle between 0 and 90
        shl     bx,2
        mov     eax,CosTable[bx] ;look up cosine
        neg     eax             ;negative in this quadrant
        neg     bx              ;sin(Angle) = cos(90-Angle)
        mov     edx,CosTable[bx+90*10*4] ;look up cosine
        jmp     short CSDone

        align   ALIGNMENT
BottomHalf:                     ;quadrant 2 or 3
        neg     bx
        add     bx,360*10       ;convert to angle between 0 and 180
        cmp     bx,90*10        ;quadrant 2 or 3
        ja      short Quadrant2
                                ;quadrant 3
        shl     bx,2
        mov     eax,CosTable[bx] ;look up cosine
        neg     bx              ;sin(Angle) = cos(90-Angle)
        mov     edx,CosTable[90*10*4+bx] ;look up sine
        neg     edx             ;negative in this quadrant
        jmp     short CSDone

        align   ALIGNMENT
Quadrant2:
        neg     bx
        add     bx,180*10       ;convert to angle between 0 and 90
        shl     bx,2
        mov     eax,CosTable[bx] ;look up cosine
        neg     eax             ;negative in this quadrant
        neg     bx              ;sin(Angle) = cos(90-Angle)
        mov     edx,CosTable[90*10*4+bx] ;look up sine
        neg     edx             ;negative in this quadrant
CSDone:
        mov     bx,[bp].Cos
        mov     [bx],eax
        mov     bx,[bp].Sin
        mov     [bx],edx

else ;!USE386

        mov     bx,[bp].Angle
        and     bx,bx           ;make sure angle's between 0 and 2*pi
        jns     short CheckInRange
MakePos:                        ;less than 0, so make it positive
        add     bx,360*10
        js      short MakePos
        jmp     short CheckInRange

        align   ALIGNMENT
MakeInRange:                    ;make sure angle is no more than 2*pi
        sub     bx,360*10
CheckInRange:
        cmp     bx,360*10
        jg      short MakeInRange

        cmp     bx,180*10       ;figure out which quadrant
        ja      short BottomHalf ;quadrant 2 or 3
        cmp     bx,90*10        ;quadrant 0 or 1
        ja      short Quadrant1
                                ;quadrant 0
        shl     bx,2
        mov     ax,word ptr CosTable[bx] ;look up sine
        mov     dx,word ptr CosTable[bx+2]
        neg     bx              ;sin(Angle) = cos(90-Angle)
        mov     cx,word ptr CosTable[bx+90*10*4+2] ;look up cosine
        mov     bx,word ptr CosTable[bx+90*10*4]
        jmp     CSDone

        align   ALIGNMENT
Quadrant1:
        neg     bx
        add     bx,180*10       ;convert to angle between 0 and 90
        shl     bx,2
        mov     ax,word ptr CosTable[bx] ;look up cosine
        mov     dx,word ptr CosTable[bx+2]
        neg     dx              ;negative in this quadrant
        neg     ax
        sbb     dx,0
        neg     bx              ;sin(Angle) = cos(90-Angle)
        mov     cx,word ptr CosTable[bx+90*10*4+2] ;look up cosine
        mov     bx,word ptr CosTable[bx+90*10*4]
        jmp     short CSDone

        align   ALIGNMENT
BottomHalf:                     ;quadrant 2 or 3
        neg     bx
        add     bx,360*10       ;convert to angle between 0 and 180
        cmp     bx,90*10        ;quadrant 2 or 3
        ja      short Quadrant2
                                ;quadrant 3
        shl     bx,2
        mov     ax,word ptr CosTable[bx] ;look up cosine
        mov     dx,word ptr CosTable[bx+2]
        neg     bx              ;sin(Angle) = cos(90-Angle)
        mov     cx,word ptr CosTable[90*10*4+bx+2] ;look up sine
        mov     bx,word ptr CosTable[90*10*4+bx]
        neg     cx              ;negative in this quadrant
        neg     bx
        sbb     cx,0
        jmp     short CSDone

        align   ALIGNMENT
Quadrant2:
        neg     bx
        add     bx,180*10       ;convert to angle between 0 and 90
        shl     bx,2
        mov     ax,word ptr CosTable[bx] ;look up cosine
        mov     dx,word ptr CosTable[bx+2]
        neg     dx              ;negative in this quadrant
        neg     ax
        sbb     dx,0
        neg     bx              ;sin(Angle) = cos(90-Angle)
        mov     cx,word ptr CosTable[90*10*4+bx+2] ;look up sine
        mov     bx,word ptr CosTable[90*10*4+bx]
        neg     cx              ;negative in this quadrant
        neg     bx
        sbb     cx,0
CSDone:
        push    bx
        mov     bx,[bp].Cos
        mov     [bx],ax
        mov     [bx+2],dx
        mov     bx,[bp].Sin
        pop     ax
        mov     [bx],ax
        mov     [bx+2],cx

endif ;USE386

        pop     bp              ;restore stack frame
        ret
_CosSin endp

;=====================================================================
; Matrix multiplies Xform by SourceVec, and stores the result in
; DestVec. Multiplies a 4x4 matrix times a 4x1 matrix; the result
; is a 4x1 matrix. Cheats by assuming the W coord is 1 and the
; bottom row of the matrix is 0 0 0 1, and doesn't bother to set
; the W coordinate of the destination.
; C near-callable as:
;       void XformVec(Xform WorkingXform, Fixedpoint *SourceVec,
;               Fixedpoint *DestVec);
;
; This assembly code is equivalent to this C code:
;   int i;
;
;   for (i=0; i<3; i++)
;      DestVec[i] = FixedMul(WorkingXform[i][0], SourceVec[0]) +
;            FixedMul(WorkingXform[i][1], SourceVec[1]) +
;            FixedMul(WorkingXform[i][2], SourceVec[2]) +
;            WorkingXform[i][3];   /* no need to multiply by W = 1 */

XVparms struc
        dw      2 dup(?)        ;return address & pushed BP
WorkingXform dw ?               ;pointer to transform matrix
SourceVec dw    ?               ;pointer to source vector
DestVec dw      ?               ;pointer to destination vector
XVparms ends

; Macro for non-386 multiply. AX, BX, CX, DX destroyed.
FIXED_MUL       MACRO   M1,M2
        local   CheckSecondOperand,SaveSignStatus,FixedMulDone

                                ;do four partial products and
                                ; add them together, accumulating
                                ; the result in CX:BX
                                ;figure out signs, so we can use
                                ; unsigned multiplies
        sub     cx,cx           ;assume both operands positive
        mov     bx,word ptr [&M1&+2]
        and     bx,bx           ;first operand negative?
        jns     short CheckSecondOperand ;no
        neg     bx              ;yes, so negate first operand
        neg     word ptr [&M1&]
        sbb     bx,0
        mov     word ptr [&M1&+2],bx
        inc     cx              ;mark that first operand is negative
CheckSecondOperand:
        mov     bx,word ptr [&M2&+2]
        and     bx,bx           ;second operand negative?
        jns     short SaveSignStatus  ;no
        neg     bx              ;yes, so negate second operand
        neg     word ptr [&M2&]
        sbb     bx,0
        mov     word ptr [&M2&+2],bx
        xor     cx,1            ;mark that second operand is negative
SaveSignStatus:
        push    cx              ;remember sign of result; 1 if result
                                ; negative, 0 if result nonnegative
        mov     ax,word ptr [&M1&+2] ;high word times high word
        mul     word ptr [&M2&+2]
        mov     cx,ax           ;
                                ;assume no overflow into DX
        mov     ax,word ptr [&M1&+2] ;high word times low word
        mul     word ptr [&M2&]
        mov     bx,ax
        add     cx,dx
        mov     ax,word ptr [&M1&] ;low word times high word
        mul     word ptr [&M2&+2]
        add     bx,ax
        adc     cx,dx
        mov     ax,word ptr [&M1&] ;low word times low word
        mul     word ptr [&M2&]
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
        mov     dx,cx
        mov     ax,bx
        pop     cx
        and     cx,cx           ;is the result negative?
        jz      short FixedMulDone ;no, we're all set
        neg     dx              ;yes, so negate DX:AX
        neg     ax
        sbb     dx,0
FixedMulDone:
        ENDM

        align   ALIGNMENT
        public _XformVec
_XformVec       proc    near
        push    bp              ;preserve stack frame
        mov     bp,sp           ;set up local stack frame
        push    si              ;preserve register variables
        push    di

if USE386

        mov     si,[bp].WorkingXform ;SI points to xform matrix
        mov     bx,[bp].SourceVec    ;BX points to source vector
        mov     di,[bp].DestVec      ;DI points to dest vector

soff=0
doff=0
        REPT 3                  ;do once each for dest X, Y, and Z
        mov     eax,[si+soff]   ;column 0 entry on this row
        imul    dword ptr [bx]  ;xform entry times source X entry
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
        shrd    eax,edx,16      ;shift the result back to 16.16 form
        mov     ecx,eax         ;set running total

        mov     eax,[si+soff+4] ;column 1 entry on this row
        imul    dword ptr [bx+4] ;xform entry times source Y entry
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
        shrd    eax,edx,16      ;shift the result back to 16.16 form
        add     ecx,eax         ;running total for this row

        mov     eax,[si+soff+8] ;column 2 entry on this row
        imul    dword ptr [bx+8] ;xform entry times source Z entry
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
        shrd    eax,edx,16      ;shift the result back to 16.16 form
        add     ecx,eax         ;running total for this row

        add     ecx,[si+soff+12] ;add in translation
        mov     [di+doff],ecx   ;save the result in the dest vector
soff=soff+16
doff=doff+4
        ENDM

else ;!USE386

        mov     si,[bp].WorkingXform ;SI points to xform matrix
        mov     di,[bp].SourceVec    ;DI points to source vector
        mov     bx,[bp].DestVec      ;BX points to dest vector
        push    bp                   ;preserve stack frame pointer

soff=0
doff=0
        REPT 3                  ;do once each for dest X, Y, and Z
        push    bx              ;remember dest vector pointer
        push    word ptr [si+soff+2] 
        push    word ptr [si+soff]
        push    word ptr [di+2]
        push    word ptr [di]
        call    _FixedMul       ;xform entry times source X entry
        add     sp,8            ;clear parameters from stack
        mov     cx,ax           ;set running total
        mov     bp,dx

        push    cx              ;preserve low word of running total
        push    word ptr [si+soff+4+2] 
        push    word ptr [si+soff+4]
        push    word ptr [di+4+2]
        push    word ptr [di+4]
        call    _FixedMul       ;xform entry times source Y entry
        add     sp,8            ;clear parameters from stack
        pop     cx              ;restore low word of running total
        add     cx,ax           ;running total for this row
        adc     bp,dx

        push    cx              ;preserve low word of running total
        push    word ptr [si+soff+8+2] 
        push    word ptr [si+soff+8]
        push    word ptr [di+8+2]
        push    word ptr [di+8]
        call    _FixedMul       ;xform entry times source Z entry
        add     sp,8            ;clear parameters from stack
        pop     cx              ;restore low word of running total
        add     cx,ax           ;running total for this row
        adc     bp,dx

        add     cx,[si+soff+12] ;add in translation
        adc     bp,[si+soff+12+2]
        pop     bx              ;restore dest vector pointer
        mov     [bx+doff],cx    ;save the result in the dest vector
        mov     [bx+doff+2],bp
soff=soff+16
doff=doff+4
        ENDM

        pop     bp              ;restore stack frame pointer

endif ;USE386

        pop     di              ;restore register variables
        pop     si
        pop     bp              ;restore stack frame
        ret
_XformVec       endp

;=====================================================================
; Matrix multiplies SourceXform1 by SourceXform2 and stores the
; result in DestXform. Multiplies a 4x4 matrix times a 4x4 matrix;
; the result is a 4x4 matrix. Cheats by assuming the bottom row of
; each matrix is 0 0 0 1, and doesn't bother to set the bottom row
; of the destination.
; C near-callable as:
;       void ConcatXforms(Xform SourceXform1, Xform SourceXform2,
;               Xform DestXform)
;
; This assembly code is equivalent to this C code:
;   int i, j;
;
;   for (i=0; i<3; i++) {
;      for (j=0; j<3; j++)
;         DestXform[i][j] =
;               FixedMul(SourceXform1[i][0], SourceXform2[0][j]) +
;               FixedMul(SourceXform1[i][1], SourceXform2[1][j]) +
;               FixedMul(SourceXform1[i][2], SourceXform2[2][j]);
;      DestXform[i][3] =
;            FixedMul(SourceXform1[i][0], SourceXform2[0][3]) +
;            FixedMul(SourceXform1[i][1], SourceXform2[1][3]) +
;            FixedMul(SourceXform1[i][2], SourceXform2[2][3]) +
;            SourceXform1[i][3];
;   }

CXparms struc
        dw      2 dup(?)        ;return address & pushed BP
SourceXform1 dw ?               ;pointer to first source xform matrix
SourceXform2 dw ?               ;pointer to second source xform matrix
DestXform    dw ?               ;pointer to destination xform matrix
CXparms ends

        align   ALIGNMENT
        public _ConcatXforms
_ConcatXforms   proc    near
        push    bp              ;preserve stack frame
        mov     bp,sp           ;set up local stack frame
        push    si              ;preserve register variables
        push    di

if USE386

        mov     bx,[bp].SourceXform2 ;BX points to xform2 matrix
        mov     si,[bp].SourceXform1 ;SI points to xform1 matrix
        mov     di,[bp].DestXform    ;DI points to dest xform matrix

roff=0                          ;row offset
        REPT 3                  ;once for each row
coff=0                          ;column offset
        REPT 3                  ;once for each of the first 3 columns,
                                ; assuming 0 as the bottom entry (no
                                ; translation)
        mov     eax,[si+roff]   ;column 0 entry on this row
        imul    dword ptr [bx+coff] ;times row 0 entry in column
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
        shrd    eax,edx,16      ;shift the result back to 16.16 form
        mov     ecx,eax         ;set running total

        mov     eax,[si+roff+4] ;column 1 entry on this row
        imul    dword ptr [bx+coff+16] ;times row 1 entry in col
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
        shrd    eax,edx,16      ;shift the result back to 16.16 form
        add     ecx,eax         ;running total

        mov     eax,[si+roff+8] ;column 2 entry on this row
        imul    dword ptr [bx+coff+32] ;times row 2 entry in col
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
        shrd    eax,edx,16      ;shift the result back to 16.16 form
        add     ecx,eax         ;running total

        mov     [di+coff+roff],ecx ;save the result in dest matrix
coff=coff+4                     ;point to next col in xform2 & dest
        ENDM
                                ;now do the fourth column, assuming
                                ; 1 as the bottom entry, causing
                                ; translation to be performed
        mov     eax,[si+roff]   ;column 0 entry on this row
        imul    dword ptr [bx+coff] ;times row 0 entry in column
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
        shrd    eax,edx,16      ;shift the result back to 16.16 form
        mov     ecx,eax         ;set running total

        mov     eax,[si+roff+4] ;column 1 entry on this row
        imul    dword ptr [bx+coff+16] ;times row 1 entry in col
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
        shrd    eax,edx,16      ;shift the result back to 16.16 form
        add     ecx,eax         ;running total

        mov     eax,[si+roff+8] ;column 2 entry on this row
        imul    dword ptr [bx+coff+32] ;times row 2 entry in col
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
        shrd    eax,edx,16      ;shift the result back to 16.16 form
        add     ecx,eax         ;running total

        add     ecx,[si+roff+12] ;add in translation

        mov     [di+coff+roff],ecx ;save the result in dest matrix
coff=coff+4                     ;point to next col in xform2 & dest

roff=roff+16                    ;point to next col in xform2 & dest
        ENDM

else ;!USE386

        mov     di,[bp].SourceXform2 ;DI points to xform2 matrix
        mov     si,[bp].SourceXform1 ;SI points to xform1 matrix
        mov     bx,[bp].DestXform    ;BX points to dest xform matrix
        push    bp                   ;preserve stack frame pointer

roff=0                          ;row offset
        REPT 3                  ;once for each row
coff=0                          ;column offset
        REPT 3                  ;once for each of the first 3 columns,
                                ; assuming 0 as the bottom entry (no
                                ; translation)
        push    bx              ;remember dest vector pointer
        push    word ptr [si+roff+2] 
        push    word ptr [si+roff]
        push    word ptr [di+coff+2]
        push    word ptr [di+coff]
        call    _FixedMul       ;column 0 entry on this row times row 0
                                ; entry in column
        add     sp,8            ;clear parameters from stack
        mov     cx,ax           ;set running total
        mov     bp,dx

        push    cx              ;preserve low word of running total
        push    word ptr [si+roff+4+2] 
        push    word ptr [si+roff+4]
        push    word ptr [di+coff+16+2]
        push    word ptr [di+coff+16]
        call    _FixedMul       ;column 1 entry on this row times row 1
                                ; entry in column
        add     sp,8            ;clear parameters from stack
        pop     cx              ;restore low word of running total
        add     cx,ax           ;running total for this row
        adc     bp,dx

        push    cx              ;preserve low word of running total
        push    word ptr [si+roff+8+2] 
        push    word ptr [si+roff+8]
        push    word ptr [di+coff+32+2]
        push    word ptr [di+coff+32]
        call    _FixedMul       ;column 1 entry on this row times row 1
                                ; entry in column
        add     sp,8            ;clear parameters from stack
        pop     cx              ;restore low word of running total
        add     cx,ax           ;running total for this row
        adc     bp,dx

        pop     bx              ;restore DestXForm pointer
        mov     [bx+coff+roff],cx ;save the result in dest matrix
        mov     [bx+coff+roff+2],bp
coff=coff+4                     ;point to next col in xform2 & dest
        ENDM
                                ;now do the fourth column, assuming
                                ; 1 as the bottom entry, causing
                                ; translation to be performed
        push    bx              ;remember dest vector pointer
        push    word ptr [si+roff+2] 
        push    word ptr [si+roff]
        push    word ptr [di+coff+2]
        push    word ptr [di+coff]
        call    _FixedMul       ;column 0 entry on this row times row 0
                                ; entry in column
        add     sp,8            ;clear parameters from stack
        mov     cx,ax           ;set running total
        mov     bp,dx

        push    cx              ;preserve low word of running total
        push    word ptr [si+roff+4+2] 
        push    word ptr [si+roff+4]
        push    word ptr [di+coff+16+2]
        push    word ptr [di+coff+16]
        call    _FixedMul       ;column 1 entry on this row times row 1
                                ; entry in column
        add     sp,8            ;clear parameters from stack
        pop     cx              ;restore low word of running total
        add     cx,ax           ;running total for this row
        adc     bp,dx

        push    cx              ;preserve low word of running total
        push    word ptr [si+roff+8+2] 
        push    word ptr [si+roff+8]
        push    word ptr [di+coff+32+2]
        push    word ptr [di+coff+32]
        call    _FixedMul       ;column 1 entry on this row times row 1
                                ; entry in column
        add     sp,8            ;clear parameters from stack
        pop     cx              ;restore low word of running total
        add     cx,ax           ;running total for this row
        adc     bp,dx

        add     cx,[si+roff+12] ;add in translation
        add     bp,[si+roff+12+2]

        pop     bx              ;restore DestXForm pointer
        mov     [bx+coff+roff],cx ;save the result in dest matrix
        mov     [bx+coff+roff+2],bp
coff=coff+4                     ;point to next col in xform2 & dest

roff=roff+16                    ;point to next col in xform2 & dest
        ENDM

        pop     bp              ;restore stack frame pointer

endif ;USE386

        pop     di              ;restore register variables
        pop     si
        pop     bp              ;restore stack frame
        ret
_ConcatXforms   endp
        end
