COMMENT /
        Fixed-point math functions and 3D transforms
        Copyright (c) 1993,94 by Alessandro Scotti
/
WARN    PRO
P386
JUMPS
LOCALS

INCLUDE MATH.INC

PUBLIC  tdFixedMul
PUBLIC  tdGetNormal
PUBLIC  tdRotate
PUBLIC  tdGetSurfaceLight
PUBLIC  tdSetLight
PUBLIC  tdSetRotation
PUBLIC  tdSetTranslation
PUBLIC  tdTransform
PUBLIC  tdTransformToImage
PUBLIC  tdTransformLight
PUBLIC  tdBackPlaneCull
PUBLIC  tdSetPerspective

;-----------------------------------------------------------
;
; Data segment
;
MATH_DATA       SEGMENT USE16 PARA PUBLIC 'DATA'
                ASSUME ds:MATH_DATA

INCLUDE         SINCOS.INC              ; Fixed 8:24 sin/cos table

XRotation       TPOINT  <>              ; 3x3 rotation matrix
YRotation       TPOINT  <>
ZRotation       TPOINT  <>

Translation     TPOINT  <>              ; Translation vector

Light           TPOINT  <>              ; Light vector
AmbientLight    DW      00              ; Ambient light

XScale                  DD      10000h  ; Scaling factor for X coordinate
YScale                  DD      10000h  ; Scaling factor for Y coordinate
PerspectiveDistance     DD      20000000h

MATH_DATA       ENDS

;-----------------------------------------------------------
;
; Code segment
;
MATH_TEXT       SEGMENT USE16 PARA PUBLIC 'CODE'
                ASSUME cs:MATH_TEXT, es:NOTHING, fs:NOTHING

tdSetPerspective        PROC PASCAL FAR
        ARG     Perspective:DWORD,      \
                ScaleX:DWORD,           \
                ScaleY:DWORD
        USES    ds

        mov     ax, SEG MATH_DATA
        mov     ds, ax
        ASSUME  ds:MATH_DATA

        mov     eax, [Perspective]
        mov     [PerspectiveDistance], eax
        mov     eax, [ScaleX]
        mov     [XScale], eax
        mov     eax, [ScaleY]
        mov     [YScale], eax

        ret
tdSetPerspective        ENDP


;-----------------------------------------------------------
;
; Sets the rotation matrix.
;
; Input:
;       RX      = X-axis rotation angle
;       RY      = X-axis rotation angle
;       RZ      = X-axis rotation angle
; Output:
;       none
;
tdSetRotation   PROC PASCAL FAR
        ARG     RX:WORD,        \
                RY:WORD,        \
                RZ:WORD
        USES    ds, si, di

        mov     ax, SEG MATH_DATA
        mov     ds, ax
        ASSUME  ds:MATH_DATA

        mov     bx, [RZ]
        mov     si, [RY]
        mov     di, [RX]
        shl     bx, 2
        shl     si, 2
        shl     di, 2

        push    ebp                     ; We use EBP as a scratch register

; Set X rotation
        mov     eax, tblCos[bx]
        imul    tblCos[si]
        mov     [XRotation.X], edx

        mov     eax, tblSin[bx]
        imul    tblCos[si]
        mov     [XRotation.Y], edx

        mov     eax, tblSin[si]
        sar     eax, 8                  ; Convert fixed 8:24 to fixed 16:16
        mov     [XRotation.Z], eax

; Set Y rotation
        mov     eax, tblCos[bx]
        imul    tblSin[si]              ; EDX:EAX = fixed 16:48
        shrd    eax, edx, 24            ; EAX = fixed 8:24
        imul    tblSin[di]              ; EDX:EAX = fixed 16:48
        mov     ebp, eax
        mov     ecx, edx
        mov     eax, tblSin[bx]
        imul    tblCos[di]
        add     eax, ebp
        adc     edx, ecx                ; EDX:EAX = fixed 16:48
        neg     edx
        mov     [YRotation.X], edx

        mov     eax, tblSin[bx]
        imul    tblSin[si]
        shrd    eax, edx, 24
        imul    tblSin[di]
        mov     ebp, eax
        mov     ecx, edx
        mov     eax, tblCos[bx]
        imul    tblCos[di]
        sub     eax, ebp
        sbb     edx, ecx
        mov     [YRotation.Y], edx

        mov     eax, tblCos[si]
        imul    tblSin[di]
        mov     [YRotation.Z], edx

; Set Z rotation
        mov     eax, tblCos[bx]
        imul    tblSin[si]
        shrd    eax, edx, 24
        imul    tblCos[di]
        mov     ebp, eax
        mov     ecx, edx
        mov     eax, tblSin[bx]
        imul    tblSin[di]
        sub     eax, ebp
        sbb     edx, ecx
        mov     [ZRotation.X], edx

        mov     eax, tblSin[bx]
        imul    tblSin[si]
        shrd    eax, edx, 24
        imul    tblCos[di]
        mov     ebp, eax
        mov     ecx, edx
        mov     eax, tblCos[bx]
        imul    tblSin[di]
        add     eax, ebp
        add     edx, ecx
        neg     edx
        mov     [ZRotation.Y], edx

        mov     eax, tblCos[si]
        imul    tblCos[di]
        mov     [ZRotation.Z], edx

        pop     ebp                     ; Restore EBP

        ret
tdSetRotation   ENDP

;-----------------------------------------------------------
;
; Sets the translation vector.
;
; Input:
;       TV      = pointer to translation vector
; Output:
;       none
;
tdSetTranslation        PROC PASCAL FAR
        ARG     TV:DWORD
        USES    ds, es, di

        mov     ax, SEG MATH_DATA
        mov     ds, ax
        ASSUME  ds:MATH_DATA

        les     di, [TV]
        mov     eax, es:[di].X
        mov     [Translation.X], eax
        mov     eax, es:[di].Y
        mov     [Translation.Y], eax
        mov     eax, es:[di].Z
        mov     [Translation.Z], eax

        ret
tdSetTranslation        ENDP

;-----------------------------------------------------------
;
; Transforms an array of TPOINT.
;
; Input:
;       Source  = pointer to source array of TPOINT
;       Dest    = pointer to destination array of TPOINT
;       Count   = number of entries to transform
; Output:
;       none
;
tdTransform     PROC PASCAL FAR
        ARG     Source:DWORD,   \
                Dest:DWORD,     \
                Count:WORD
        LOCAL   Adjust:DWORD
        USES    ds, si, es, di, fs

        mov     ax, SEG MATH_DATA
        mov     ds, ax
        ASSUME  ds:MATH_DATA

        lfs     si, [Source]
        les     di, [Dest]

        ALIGN   DWORD
@@Loop:
; Transform Z coordinate
        mov     eax, fs:[si].X
        imul    [ZRotation.X]
        mov     ecx, eax
        mov     ebx, edx
        mov     eax, fs:[si].Y
        imul    [ZRotation.Y]
        add     ecx, eax
        adc     ebx, edx
        mov     eax, fs:[si].Z
        imul    [ZRotation.Z]
        add     eax, ecx
        adc     edx, ebx
        mov     ebx, eax
        shrd    eax, edx, 16
        add     eax, [Translation.Z]    ; EAX = new Z coord (fixed 16:16)
        mov     es:[di].Z, eax
; Get perspective factor
        mov     ebx, [PerspectiveDistance]
        sub     eax, ebx
        neg     eax                     ; EAX = PD - Z
        xor     edx, edx
        shld    edx, eax, 16
        shl     eax, 16
        idiv    ebx                     ; EAX = fixed 16:16 result
        mov     [Adjust], eax

; Transform X coordinate
        mov     eax, fs:[si].X
        imul    [XRotation.X]
        mov     ecx, eax
        mov     ebx, edx
        mov     eax, fs:[si].Y
        imul    [XRotation.Y]
        add     ecx, eax
        adc     ebx, edx
        mov     eax, fs:[si].Z
        imul    [XRotation.Z]
        add     eax, ecx
        adc     edx, ebx
        shrd    eax, edx, 16
        add     eax, [Translation.X]
        imul    [Adjust]
        shrd    eax, edx, 16
        mov     es:[di].X, eax

; Transform Y coordinate
        mov     eax, fs:[si].X
        imul    [YRotation.X]
        mov     ecx, eax
        mov     ebx, edx
        mov     eax, fs:[si].Y
        imul    [YRotation.Y]
        add     ecx, eax
        adc     ebx, edx
        mov     eax, fs:[si].Z
        imul    [YRotation.Z]
        add     eax, ecx
        adc     edx, ebx
        shrd    eax, edx, 16
        add     eax, [Translation.Y]
        imul    [Adjust]
        shrd    eax, edx, 16
        mov     es:[di].Y, eax

        add     si, SIZE TPOINT
        add     di, SIZE TPOINT
        dec     [Count]
        jnz     @@Loop

        ret
tdTransform     ENDP

;-----------------------------------------------------------
;
; Transforms an array of TPOINT into an array of TIMAGEPOINT.
;
; Input:
;       Source  = pointer to source array of TPOINT
;       Dest    = pointer to destination array of TIMAGEPOINT
;       Count   = number of entries to transform
;       DeltaX  = translation distance for the X coordinate
;       DeltaY  = translation distance for the Y coordinate
; Output:
;       the maximum Z value
;
tdTransformToImage      PROC PASCAL FAR
        ARG     Source:DWORD,   \
                Dest:DWORD,     \
                Count:WORD,     \
                DeltaX:WORD,    \
                DeltaY:WORD
        LOCAL   Adjust:DWORD,   \
                Max:DWORD
        USES    ds, si, es, di, fs

        mov     ax, SEG MATH_DATA
        mov     ds, ax
        ASSUME  ds:MATH_DATA

        lfs     si, [Source]
        les     di, [Dest]
        mov     [Max], 80000000h

@@Loop:
; Check max Z
        mov     eax, fs:[si].Z
        cmp     eax, [Max]
        jle     @@1
        mov     [Max], eax
@@1:

; Transform X coordinate
        mov     ax, WORD PTR fs:[si].X[2]
        add     ax, [DeltaX]
        mov     es:[di].IX, ax

; Transform Y coordinate
        mov     ax, WORD PTR fs:[si].Y[2]
        add     ax, [DeltaY]
        mov     es:[di].IY, ax

        add     si, SIZE TPOINT
        add     di, SIZE TIMAGEPOINT
        dec     [Count]
        jnz     @@Loop

        mov     eax, [Max]
        shld    edx, eax, 16
        ret
tdTransformToImage      ENDP

;-----------------------------------------------------------
;
; Sets the light source.
;
; Input:
;       Light   = pointer to light vector
; Output:
;       none
;
tdSetLight      PROC PASCAL FAR
        ARG     L:DWORD
        USES    ds, es, di

        mov     ax, SEG MATH_DATA
        mov     ds, ax
        ASSUME  ds:MATH_DATA

        les     di, [L]
        mov     eax, es:[di].X
        mov     [Light.X], eax
        mov     eax, es:[di].Y
        mov     [Light.Y], eax
        mov     eax, es:[di].Z
        mov     [Light.Z], eax

        ret
tdSetLight      ENDP

;-----------------------------------------------------------
;
; Computes light intensity for an array of surfaces.
;
; Input:
;       Normals = pointer to an array of surface normals
;       Lights  = pointer to an array of integer to be filled with
;                 light intensity
;       Count   = number of elements to transform
; Output:
;       none
;
tdTransformLight        PROC PASCAL FAR
        ARG     Normals:DWORD,  \
                Lights:DWORD,   \
                Count:WORD
        USES    ds, si, es, di, fs

        mov     ax, SEG MATH_DATA
        mov     fs, ax
        ASSUME  fs:MATH_DATA

        lds     si, [Normals]
        les     di, [Lights]
        ASSUME  ds:NOTHING

; Intensity is given by the dot product between the Light vector and
; the surface normal
@@Loop:
        mov     eax, ds:[si].Z
        imul    [Light.Z]
        mov     ebx, eax
        mov     ecx, edx
        mov     eax, ds:[si].Y
        imul    [Light.Y]
        add     ebx, eax
        adc     ecx, edx
        mov     eax, ds:[si].X
        imul    [Light.X]
        add     eax, ebx
        adc     edx, ecx                ; EDX:EAX = fixed 32:32 intensity
        add     dx, [AmbientLight]
        test    dx, dx
        jg      @@1
        xor     dx, dx                  ; Return 0 for no light
@@1:
        mov     es:[di], dx
        inc     di
        inc     di
        add     si, SIZE TPOINT
        dec     [Count]
        jnz     @@Loop

        ASSUME  fs:NOTHING
        ret
tdTransformLight        ENDP

;-----------------------------------------------------------
;
; Returns the light value given the normal to a surface.
;
; Input:
;       Normal  = pointer to TPOINT surface normal vector
; Output:
;       AX      = light intensity (>=0)
; Notes:
;       the normal is rotated according to the current setting.
;
tdGetSurfaceLight       PROC PASCAL FAR
        ARG     Normal:DWORD
        USES    ds, esi, es, di

        mov     ax, SEG MATH_DATA
        mov     ds, ax
        ASSUME  ds:MATH_DATA

        les     di, [Normal]

; Transform Z coordinate
        mov     eax, es:[di].X
        imul    [ZRotation.X]
        mov     ecx, eax
        mov     ebx, edx
        mov     eax, es:[di].Y
        imul    [ZRotation.Y]
        add     ecx, eax
        adc     ebx, edx
        mov     eax, es:[di].Z
        imul    [ZRotation.Z]
        add     eax, ecx
        adc     edx, ebx
        shrd    eax, edx, 16
        imul    [Light.Z]
        shrd    eax, edx, 16
        mov     esi, eax

; Transform X coordinate
        mov     eax, es:[di].X
        imul    [XRotation.X]
        mov     ecx, eax
        mov     ebx, edx
        mov     eax, es:[di].Y
        imul    [XRotation.Y]
        add     ecx, eax
        adc     ebx, edx
        mov     eax, es:[di].Z
        imul    [XRotation.Z]
        add     eax, ecx
        adc     edx, ebx
        shrd    eax, edx, 16
        imul    [Light.X]
        shrd    eax, edx, 16
        add     esi, eax

; Transform Y coordinate
        mov     eax, es:[di].X
        imul    [YRotation.X]
        mov     ecx, eax
        mov     ebx, edx
        mov     eax, es:[di].Y
        imul    [YRotation.Y]
        add     ecx, eax
        adc     ebx, edx
        mov     eax, es:[di].Z
        imul    [YRotation.Z]
        add     eax, ecx
        adc     edx, ebx
        shrd    eax, edx, 16
        imul    [Light.X]
        shrd    eax, edx, 16
        add     eax, esi
        shr     eax, 16

; Add ambient light
        add     ax, [AmbientLight]
        test    ax, ax
        jge     @@Exit
        xor     ax, ax

@@Exit:
        ret
tdGetSurfaceLight       ENDP

;-----------------------------------------------------------
;
; Rotates an array of TPOINT.
;
; Input:
;       Source  = pointer to source array of TPOINT
;       Dest    = pointer to destination array of TPOINT
;       Count   = number of entries to transform
; Output:
;       none
;
tdRotate        PROC PASCAL FAR
        ARG     Source:DWORD,   \
                Dest:DWORD,     \
                Count:WORD
        USES    ds, si, es, di, fs

        mov     ax, SEG MATH_DATA
        mov     ds, ax
        ASSUME  ds:MATH_DATA

        lfs     si, [Source]
        les     di, [Dest]

@@Loop:
; Transform Z coordinate
        mov     eax, fs:[si].X
        imul    [ZRotation.X]
        mov     ecx, eax
        mov     ebx, edx
        mov     eax, fs:[si].Y
        imul    [ZRotation.Y]
        add     ecx, eax
        adc     ebx, edx
        mov     eax, fs:[si].Z
        imul    [ZRotation.Z]
        add     eax, ecx
        adc     edx, ebx
        shrd    eax, edx, 16
        mov     es:[di].Z, eax

; Transform X coordinate
        mov     eax, fs:[si].X
        imul    [XRotation.X]
        mov     ecx, eax
        mov     ebx, edx
        mov     eax, fs:[si].Y
        imul    [XRotation.Y]
        add     ecx, eax
        adc     ebx, edx
        mov     eax, fs:[si].Z
        imul    [XRotation.Z]
        add     eax, ecx
        adc     edx, ebx
        shrd    eax, edx, 16
        mov     es:[di].X, eax

; Transform Y coordinate
        mov     eax, fs:[si].X
        imul    [YRotation.X]
        mov     ecx, eax
        mov     ebx, edx
        mov     eax, fs:[si].Y
        imul    [YRotation.Y]
        add     ecx, eax
        adc     ebx, edx
        mov     eax, fs:[si].Z
        imul    [YRotation.Z]
        add     eax, ecx
        adc     edx, ebx
        shrd    eax, edx, 16
        mov     es:[di].Y, eax

        add     si, SIZE TPOINT
        add     di, SIZE TPOINT
        dec     [Count]
        jnz     @@Loop

        ret
tdRotate        ENDP

tdFixedMul      PROC PASCAL FAR
        ARG     F1:DWORD,       \
                F2:DWORD

        mov     eax, [F1]
        imul    [F2]
        shr     eax, 16

        ret
tdFixedMul      ENDP

;-----------------------------------------------------------
;
; Returns in EAX the square root of EDX:EAX.
;
subSqrt PROC NEAR
        push    esi
        push    edi

        add     eax, eax
        adc     edx, 0
        mov     eax, edx                ; Just discard the low bits

        mov     esi, eax
        xor     edi, edi
        shld    edi, esi, 16
        shl     esi, 16
@@Loop:
        mov     ebx, eax
        mul     eax
        add     eax, esi
        adc     edx, edi
        shrd    eax, edx, 1
        shr     edx, 1
        div     ebx
        cmp     eax, ebx
        jne     @@Loop

; Adjust EAX
        shl     eax, 8

        pop     edi
        pop     esi
        ret
subSqrt ENDP

;-----------------------------------------------------------
;
; Finds the unitary normal to a given surface.
;
; Input:
;       Dest            = pointer to TPOINT (vector) result
;       P1, P2, P3      = pointer to TPOINT points on surface
; Output:
;       none
; Notes:
;       the normal is given by the cross-product between (P3-P1) and
;       (P2-P1), so its orientation depends on the parameters order.
;
tdGetNormal     PROC PASCAL FAR
        ARG     Dest:DWORD,     \
                P1:DWORD,       \
                P2:DWORD,       \
                P3:DWORD
        LOCAL   V1:TPOINT,      \
                V2:TPOINT,      \
                N:TPOINT
        USES    ds, si, es, di

; Get vector V1
        lds     si, [P1]
        les     di, [P3]
        mov     eax, es:[di].X
        sub     eax, ds:[si].X
        mov     [V1.X], eax
        mov     eax, es:[di].Y
        sub     eax, ds:[si].Y
        mov     [V1.Y], eax
        mov     eax, es:[di].Z
        sub     eax, ds:[si].Z
        mov     [V1.Z], eax

; Get vector V2
        les     di, [P2]
        mov     eax, es:[di].X
        sub     eax, ds:[si].X
        mov     [V2.X], eax
        mov     eax, es:[di].Y
        sub     eax, ds:[si].Y
        mov     [V2.Y], eax
        mov     eax, es:[di].Z
        sub     eax, ds:[si].Z
        mov     [V2.Z], eax

; Get normal vector (V1 x V2)
        mov     eax, [V1.Z]
        imul    [V2.Y]
        mov     ebx, eax
        mov     ecx, edx
        mov     eax, [V1.Y]
        imul    [V2.Z]
        sub     eax, ebx
        sbb     edx, ecx
        shrd    eax, edx, 16
        mov     [N.X], eax

        mov     eax, [V1.X]
        imul    [V2.Z]
        mov     ebx, eax
        mov     ecx, edx
        mov     eax, [V1.Z]
        imul    [V2.X]
        sub     eax, ebx
        sbb     edx, ecx
        shrd    eax, edx, 16
        mov     [N.Y], eax

        mov     eax, [V1.Y]
        imul    [V2.X]
        mov     ebx, eax
        mov     ecx, edx
        mov     eax, [V1.X]
        imul    [V2.Y]
        sub     eax, ebx
        sbb     edx, ecx
        shrd    eax, edx, 16
        mov     [N.Z], eax

; Get normal length
        mov     eax, [N.X]
        imul    eax
        mov     ebx, eax
        mov     ecx, edx
        mov     eax, [N.Y]
        imul    eax
        add     ebx, eax
        adc     ecx, edx
        mov     eax, [N.Z]
        imul    eax
        add     eax, ebx
        adc     edx, ecx                ; EDX:EAX = N.X*N.X + N.Y*N.Y + N.Z*N.Z
        call    subSqrt                 ; EAX = normal length
        mov     ebx, eax

; Adjust vector and save it
        les     di, [Dest]
        mov     eax, [N.X]
        cdq
        shld    edx, eax, 16
        shl     eax, 16
        idiv    ebx
        mov     es:[di].X, eax
        mov     eax, [N.Y]
        cdq
        shld    edx, eax, 16
        shl     eax, 16
        idiv    ebx
        mov     es:[di].Y, eax
        mov     eax, [N.Z]
        cdq
        shld    edx, eax, 16
        shl     eax, 16
        idiv    ebx
        mov     es:[di].Z, eax

        ret
tdGetNormal     ENDP

TPOLY   STRUC
        Vtx     DW      4 DUP(?)
TPOLY   ENDS

;-----------------------------------------------------------
;
; Performs surface removal on an array of polygons.
;
; Input:
;       Poly    = pointer to an array of TPOLY
;       Vertex  = pointer to an array of TPOINT
;       Dest    = pointer to an array of integer
;       Count   = number of polygons to check
;       Step    = size of TPOLY structure
; Output:
;       if the n-th polygon is invisible the n-th entry of the
;       Dest array is set to -1, other entries are not modified
;       (so it's possible to use the Light array for Dest, because
;       the light intensity is always >= 0)
;
tdBackPlaneCull PROC PASCAL FAR
        ARG     Step:WORD,      \
                Poly:DWORD,     \
                Vertex:DWORD,   \
                Dest:DWORD,     \
                Count:WORD
        USES    ds, si, es, di, fs
        ASSUME  ds:NOTHING

        mov     ds, WORD PTR Vertex[2]
        les     di, [Poly]
        mov     fs, WORD PTR Dest[2]

@@Loop:
        mov     ax, es:[di].Vtx[2]      ; Index of 2nd vertex
        shl     ax, 2
        mov     bx, ax
        shl     ax, 1
        add     bx, ax                  ; BX = index*SIZE TPOINT
        add     bx, WORD PTR [Vertex]   ; BX = offset of 2nd vertex
        mov     ax, es:[di].Vtx[4]      ; Index of 3rd vertex
        shl     ax, 2
        mov     si, ax
        shl     ax, 1
        add     si, ax
        add     si, WORD PTR [Vertex]   ; SI = offset of 3rd vertex
        mov     ecx, ds:[si].X
        sub     ecx, ds:[bx].X          ; ECX = V3.X-V2.X
        mov     edx, ds:[si].Y
        sub     edx, ds:[bx].Y          ; EDX = V3.Y-V2.Y
        mov     ax, es:[di].Vtx[0]      ; Index of 1st vertex
        shl     ax, 2
        mov     si, ax
        shl     ax, 1
        add     si, ax
        add     si, WORD PTR [Vertex]   ; SI = offset of 1st vertex
        mov     eax, ds:[si].X
        sub     eax, ds:[bx].X          ; EAX = V1.X-V2.X
        mov     esi, ds:[si].Y
        sub     esi, ds:[bx].Y          ; ESI = V1.Y-V2.Y
        imul    edx
        mov     ebx, eax
        xchg    ecx, edx                ; ECX:EBX = (V1.X-V2.X)*(V3.Y-V2.Y)
        mov     eax, esi
        imul    edx                     ; EDX:EAX = (V1.Y-V2.Y)*(V3.X-V2.X)
        sub     eax, ebx
        sbb     edx, ecx
        jl      @@Next                  ; Polygon is visible
        mov     bx, WORD PTR [Dest]     ; FS:BX -> current Dest entry
        mov     WORD PTR fs:[bx], -1    ; Remove polygon
@@Next:
        add     WORD PTR [Dest], 2      ; Next entry for dest
        add     di, [Step]              ; Next polygon
        dec     [Count]
        jnz     @@Loop

        ret
tdBackPlaneCull ENDP

MATH_TEXT       ENDS
END
