**LISTING 54.1 FIXED.ASM** ; Fixed point routines. ; Tested with TASM 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) adcedx,0 ;whole part of result is in DX endif ;MUL-ROUNDING-ON shreax,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 muldi if MUL-ROUNDING-ON add ax,8000h ;round by adding 2^(-17) adcbx,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 dw 2 dup(?) ;return address & pushed BP Dividend dd? Divisor dd? FDparms ends alignALIGNMENT public_FixedDiv _FixedDivproc near pushbp movbp,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 subcx, 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] sbbax,0 inc cx ;mark that first operand is negative CheckSecondOperandD: mov bx,word ptr [bp+Divisor+2] and bx,bx ;second operand negative? jnsSaveSignStatusD;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 ;===================================================================== ; Returns the sine and cosine of an angle. ; C near-callable as: ; void CosSin(TAngle Angle, Fixedpoint *Cos, Fixedpoint *); alignALIGNMENT 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 alignALIGNMENT public _CosSin _CosSin procnear 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 CheckInRange MakePos: ;less than 0, so make it positive add bx,360*10 js 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 MakeInRange cmp bx,180*10 ;figure out which quadrant ja BottomHalf ;quadrant 2 or 3 cmp bx,90*10 ;quadrant 0 or 1 ja Quadrant1 ;quadrant 0 shl bx,2 move ax,CosTable[bx] ;look up sine neg bx;sin(Angle) = cos(90-Angle) move dx,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 move ax,CosTable[bx] ;look up cosine neg eax ;negative in this quadrant neg bx ;sin(Angle) = cos(90-Angle) move dx,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 Quadrant2 ;quadrant 3 shl bx,2 move ax,CosTable[bx] ;look up cosine neg bx;sin(Angle) = cos(90-Angle) move dx,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 move ax,CosTable[bx] ;look up cosine neg eax ;negative in this quadrant neg bx ;sin(Angle) = cos(90-Angle) move dx,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 CheckInRange MakePos: ;less than 0, so make it positive add bx,360*10 js 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 MakeInRange cmp bx,180*10 ;figure out which quadrant ja BottomHalf ;quadrant 2 or 3 cmp bx,90*10 ;quadrant 0 or 1 jaQuadrant1 ;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 jaQuadrant2 ;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 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 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 FixedMulDone ;no, we're all set neg dx ;yes, so negate DX:AX neg ax sbb dx,0 FixedMulDone: ENDM align ALIGNMENT public _XformVec _XformVecprocnear 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 move cx,eax ;set running total move ax,[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 move ax,[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 _XformVecendp ;===================================================================== ; 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 _ConcatXformsprocnear 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) move ax,[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 move cx,eax ;set running total move ax,[si+roff+4] ;column 1 entry on this row imuld word 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 move ax,[si+roff+8] ;column 2 entry on this row imuld word 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 move ax,[si+roff] ;column 0 entry on this row imuld word 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 move cx,eax ;set running total move ax,[si+roff+4] ;column 1 entry on this row imuld word 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 move ax,[si+roff+8] ;column 2 entry on this row imuld word 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 addsp,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