/******************************Module*Header*******************************\
* Module Name: mcdrend.c
*
* This file contains routines to do high-level triangle rendering for the
* Millenium MCD driver, including culling and face computations.  Note that
* in this driver, we don't use vertex color pointer at all since all pointer
* references need to be checked to avoid the possibility of an invalid
* memory reference.  Instead, we copy the color data in the cases where we
* need to during two-sided operation.  This is not the common case, and even
* in the case where the color data needs to be copied to colors[0] (and back),
* the copy only needs to be done for (on average) half the faces.
*
* Copyright (c) 1996 Microsoft Corporation
\**************************************************************************/

#include "precomp.h"
#include "mcdhw.h"
#include "mcdutil.h"
#include "mcdmath.h"
#include "math.h"

#if _X86_

#define GET_HALF_AREA(pRc, a, b, c)\
\
__asm{ mov     ecx, c                                                                           };\
__asm{ mov     eax, a                                                                           };\
__asm{ mov     ebx, b                                                                           };\
__asm{ mov     edx, pRc                                                                         };\
__asm{ fld     DWORD PTR [OFFSET(MCDVERTEX.windowCoord.x)][ecx]                                 };\
__asm{ fsub    DWORD PTR [OFFSET(MCDVERTEX.windowCoord.x)][eax]  /* dxAC                     */ };\
__asm{ fld     DWORD PTR [OFFSET(MCDVERTEX.windowCoord.y)][ecx]                                 };\
__asm{ fsub    DWORD PTR [OFFSET(MCDVERTEX.windowCoord.y)][ebx]  /* dyBC dxAC                */ };\
__asm{ fld     DWORD PTR [OFFSET(MCDVERTEX.windowCoord.x)][ecx]  /* dxBC dyBC dxAC           */ };\
__asm{ fsub    DWORD PTR [OFFSET(MCDVERTEX.windowCoord.x)][ebx]                                 };\
__asm{ fld     DWORD PTR [OFFSET(MCDVERTEX.windowCoord.y)][ecx]                                 };\
__asm{ fsub    DWORD PTR [OFFSET(MCDVERTEX.windowCoord.y)][eax]  /* dyAC dxBC dyBC dxAC      */ };\
__asm{ fxch    ST(2)                                             /* dyBC dxBC dyAC dxAC      */ };\
__asm{ fst     DWORD PTR [OFFSET(DEVRC.dyBC)][edx]                                              };\
__asm{ fmul    ST, ST(3)                                         /* dxACdyBC dxBC dyAC dxAC  */ };\
__asm{ fxch    ST(2)                                             /* dyAC dxBC dxACdyBC dxAC  */ };\
__asm{ fst     DWORD PTR [OFFSET(DEVRC.dyAC)][edx]                                              };\
__asm{ fmul    ST, ST(1)                                         /* dxBCdyAC dxBC dxACdyBC dxAC */ };\
__asm{ fxch    ST(1)                                             /* dxBC dxBCdyAC dxACdyBC dxAC */ };\
__asm{ fstp    DWORD PTR [OFFSET(DEVRC.dxBC)][edx]               /* dxBCdyAC dxACdyBC dxAC   */ };\
__asm{ fld     DWORD PTR [OFFSET(MCDVERTEX.windowCoord.x)][ebx]                                 };\
__asm{ fsub    DWORD PTR [OFFSET(MCDVERTEX.windowCoord.x)][eax]  /* dxAB dxBCdyAC dxACdyBC dxAC  */ };\
__asm{ fxch    ST(1)                                             /* dxBCdyAC dxAB dxACdyBC  dxAC */ };\
__asm{ fsubp   ST(2), ST                                         /* dxAB area dxAC */           };\
__asm{ fld     DWORD PTR [OFFSET(MCDVERTEX.windowCoord.y)][ebx]                                 };\
__asm{ fsub    DWORD PTR [OFFSET(MCDVERTEX.windowCoord.y)][eax]  /* dyAB dxAB area  dxAC */     };\
__asm{ fxch    ST(3)                                             /* dxAC dxAB area  dyAB */     };\
__asm{ fstp    DWORD PTR [OFFSET(DEVRC.dxAC)][edx]               /* dxAB area  dyAB */          };\
__asm{ fstp    DWORD PTR [OFFSET(DEVRC.dxAB)][edx]               /* area  dyAB */               };\
__asm{ fstp    DWORD PTR [OFFSET(DEVRC.halfArea)][edx]           /* dyAB */                     };\
__asm{ fstp    DWORD PTR [OFFSET(DEVRC.dyAB)][edx]               /* (empty) */                  };

#else

#define GET_HALF_AREA(pRc, a, b, c)\
    /* Compute signed half-area of the triangle */                          \
    (pRc)->dxAC = (c)->windowCoord.x - (a)->windowCoord.x;                  \
    (pRc)->dxBC = (c)->windowCoord.x - (b)->windowCoord.x;                  \
    (pRc)->dyAC = (c)->windowCoord.y - (a)->windowCoord.y;                  \
    (pRc)->dyBC = (c)->windowCoord.y - (b)->windowCoord.y;                  \
    (pRc)->dxAB = (b)->windowCoord.x - (a)->windowCoord.x;                  \
    (pRc)->dyAB = (b)->windowCoord.y - (a)->windowCoord.y;                  \
                                                                            \
    (pRc)->halfArea = (pRc)->dxAC * (pRc)->dyBC - (pRc)->dxBC * (pRc)->dyAC;

#endif


#define SORT_AND_CULL_FACE(a, b, c, face, ccw)\
{                                                                           \
    LONG reversed;                                                          \
    MCDVERTEX *temp;                                                        \
                                                                            \
                                                                            \
    reversed = 0;                                                           \
    if (__MCD_VERTEX_COMPARE((a)->windowCoord.y, <, (b)->windowCoord.y)) {      \
        if (__MCD_VERTEX_COMPARE((b)->windowCoord.y, <, (c)->windowCoord.y)) {  \
            /* Already sorted */                                            \
        } else {                                                            \
            if (__MCD_VERTEX_COMPARE((a)->windowCoord.y, <, (c)->windowCoord.y)) {\
                temp=(b); (b)=(c); (c)=temp;                                \
                reversed = 1;                                               \
            } else {                                                        \
                temp=(a); (a)=(c); (c)=(b); (b)=temp;                       \
            }                                                               \
        }                                                                   \
    } else {                                                                \
        if (__MCD_VERTEX_COMPARE((b)->windowCoord.y, <, (c)->windowCoord.y)) {  \
            if (__MCD_VERTEX_COMPARE((a)->windowCoord.y, <, (c)->windowCoord.y)) {\
                temp=(a); (a)=(b); (b)=temp;                                \
                reversed = 1;                                               \
            } else {                                                        \
                temp=(a); (a)=(b); (b)=(c); (c)=temp;                       \
            }                                                               \
        } else {                                                            \
            temp=(a); (a)=(c); (c)=temp;                                    \
            reversed = 1;                                                   \
        }                                                                   \
    }                                                                       \
                                                                            \
    GET_HALF_AREA(pRc, (a), (b), (c));                                      \
                                                                            \
    (ccw) = !__MCD_FLOAT_LTZ(pRc->halfArea);                                \
                                                                            \
    /*                                                                      \
    ** Figure out if face is culled or not.  The face check needs to be     \
    ** based on the vertex winding before sorting.  This code uses the      \
    ** reversed flag to invert the sense of ccw - an xor accomplishes       \
    ** this conversion without an if test.                                  \
    **                                                                      \
    **          ccw     reversed                xor                         \
    **          ---     --------                ---                         \
    **          0       0                       0 (remain !ccw)             \
    **          1       0                       1 (remain ccw)              \
    **          0       1                       1 (become ccw)              \
    **          1       1                       0 (become cw)               \
    */                                                                      \
    (face) = pRc->polygonFace[(ccw) ^ reversed];                            \
    if ((face) == pRc->cullFlag) {                                          \
        /* Culled */                                                        \
        return;                                                             \
    }                                                                       \
}

////////////////////////////////////////////////////////////////////////
//
// VOID FASTCALL __MCDCalcZSlope(DEVRC *pRc, MCDVERTEX *a, MCDVERTEX *b, MCDVERTEX *c)
//
// Local helper routine to calculate z slopes for z-offseting primitives.
//
////////////////////////////////////////////////////////////////////////

VOID FASTCALL __MCDCalcZSlope(DEVRC *pRc, MCDVERTEX *a, MCDVERTEX *b, MCDVERTEX *c)
{
    MCDFLOAT oneOverArea, t1, t2, t3, t4;
    MCDFLOAT dzAC, dzBC;

    if (CASTINT(pRc->halfArea) == 0) {
        pRc->dzdx = __MCDZERO;
        pRc->dzdy = __MCDZERO;
        return;
    }

    oneOverArea =  __MCDONE / pRc->halfArea;

    t1 = pRc->dyAC * oneOverArea;
    t2 = pRc->dyBC * oneOverArea;
    t3 = pRc->dxAC * oneOverArea;
    t4 = pRc->dxBC * oneOverArea;

    dzAC = c->windowCoord.z - a->windowCoord.z;
    dzBC = c->windowCoord.z - b->windowCoord.z;
    pRc->dzdx = (dzAC * t2 - dzBC * t1);
    pRc->dzdy = (dzBC * t3 - dzAC * t4);
}


////////////////////////////////////////////////////////////////////////
//
// VOID FASTCALL __MCDGetZOffsetDelta(DEVRC *pRc)
//
// Returns required z offset value for current primitive.  Assumes that
// z deltas are already in RC.
//
////////////////////////////////////////////////////////////////////////


MCDFLOAT FASTCALL __MCDGetZOffsetDelta(DEVRC *pRc)
{
#define FABS(f)  ((MCDFLOAT)fabs((double) (f)))
    MCDFLOAT maxdZ;

    // Find maximum x or y slope:

    if(FABS(pRc->dzdx) > FABS(pRc->dzdy))
        maxdZ = FABS(pRc->dzdx);
    else
        maxdZ = FABS(pRc->dzdy);

    return (pRc->MCDState.zOffsetFactor * maxdZ);
}


////////////////////////////////////////////////////////////////////////
//
// VOID FASTCALL __MCDRenderGenTriangle(DEVRC *pRc, MCDVERTEX *a,
//                                      MCDVERTEX *b, MCDVERTEX *c)
//
//
// This is the generic triangle-rendering routine.  This is used if either
// of the polygon faces are not GL_FILL.
//
////////////////////////////////////////////////////////////////////////

//!! Fix clipping logic, add startXXX logic

VOID FASTCALL __MCDRenderGenTriangle(DEVRC *pRc, MCDVERTEX *a, MCDVERTEX *b,
                                     MCDVERTEX *c)
{
    LONG ccw, face;
    MCDVERTEX *oa, *ob, *oc;
    RECTL *pClip;
    ULONG clipNum;
    MCDFLOAT zOffset;
    MCDCOLOR tempA, tempB, tempC;
    ULONG polygonMode;
    BOOL backFace;
    MCDVERTEX *pv;

//!!    MCDBG_PRINT("__MCDRenderGenTriangle");

    /*
    ** Save old vertex pointers in case we end up not doing a fill.
    */

    oa = a; ob = b; oc = c;

    SORT_AND_CULL_FACE(a, b, c, face, ccw);

    if ((clipNum = pRc->pEnumClip->c) > 1) {
        pClip = &pRc->pEnumClip->arcl[0];
        (*pRc->HWSetupClipRect)(pRc, pClip++);
    }

    polygonMode = pRc->polygonMode[face];
    backFace = (pRc->privateEnables & __MCDENABLE_TWOSIDED) &&
               (face == __MCD_BACKFACE);

    // Pick correct face color and render the triangle:

    if (pRc->privateEnables & __MCDENABLE_SMOOTH) {

        if (backFace) {
            SWAP_COLOR(a);
            SWAP_COLOR(b);
            SWAP_COLOR(c);
        }

    } else { // Flat shading

        pv = pRc->pvProvoking;

        if (polygonMode == GL_FILL) {
            if (backFace) {
                SWAP_COLOR(pv);
            }
        } else {

            SAVE_COLOR(tempA, a);
            SAVE_COLOR(tempB, b);
            SAVE_COLOR(tempC, c);

            if (backFace) {
                SWAP_COLOR(pv);
            }

            a->colors[0] = pv->colors[0];
            b->colors[0] = pv->colors[0];
            c->colors[0] = pv->colors[0];
        }
    }

    // Render triangle using the current polygon mode for the face:

    switch (pRc->polygonMode[face]) {
        case GL_FILL:
            if (CASTINT(pRc->halfArea) != 0) {
                (*pRc->drawTri)(pRc, a, b, c, (BOOL) ccw);
                while (--clipNum) {
                    (*pRc->HWSetupClipRect)(pRc, pClip++);
                    (*pRc->drawTri)(pRc, a, b, c, (BOOL) ccw);
                }
            }
            break;
        case GL_POINT:

            (*pRc->beginPointDrawing)(pRc);

            if (pRc->MCDState.enables & MCD_POLYGON_OFFSET_POINT_ENABLE) {
                __MCDCalcZSlope(pRc, a, b, c);
                zOffset = __MCDGetZOffsetDelta(pRc) + pRc->MCDState.zOffsetUnits;
                oa->windowCoord.z += zOffset;
                ob->windowCoord.z += zOffset;
                oc->windowCoord.z += zOffset;
            }

            if (oa->flags & MCDVERTEX_EDGEFLAG) {
                (*pRc->drawPoint)(pRc, oa);
            }
            if (ob->flags & MCDVERTEX_EDGEFLAG) {
                (*pRc->drawPoint)(pRc, ob);
            }
            if (oc->flags & MCDVERTEX_EDGEFLAG) {
                (*pRc->drawPoint)(pRc, oc);
            }

            if (pRc->MCDState.enables & MCD_POLYGON_OFFSET_POINT_ENABLE) {
                oa->windowCoord.z -= zOffset;
                ob->windowCoord.z -= zOffset;
                oc->windowCoord.z -= zOffset;
            }

            break;

        case GL_LINE:
            if (pRc->MCDState.enables & MCD_POLYGON_OFFSET_LINE_ENABLE) {
                __MCDCalcZSlope(pRc, a, b, c);
                zOffset = __MCDGetZOffsetDelta(pRc) + pRc->MCDState.zOffsetUnits;
                oa->windowCoord.z += zOffset;
                ob->windowCoord.z += zOffset;
                oc->windowCoord.z += zOffset;
            }

           (*pRc->beginLineDrawing)(pRc);

           if ((oa->flags & MCDVERTEX_EDGEFLAG) &&
                (ob->flags & MCDVERTEX_EDGEFLAG) &&
                (oc->flags & MCDVERTEX_EDGEFLAG)) {

                (*pRc->drawLine)(pRc, oa, ob, TRUE);
                (*pRc->drawLine)(pRc, ob, oc, 0);
                (*pRc->drawLine)(pRc, oc, oa, 0);

            } else {

                if (oa->flags & MCDVERTEX_EDGEFLAG)
                    (*pRc->drawLine)(pRc, oa, ob, TRUE);
                if (ob->flags & MCDVERTEX_EDGEFLAG)
                    (*pRc->drawLine)(pRc, ob, oc, TRUE);
                if (oc->flags & MCDVERTEX_EDGEFLAG)
                    (*pRc->drawLine)(pRc, oc, oa, TRUE);
            }

            (*pRc->endLineDrawing)(pRc);

            if (pRc->MCDState.enables & MCD_POLYGON_OFFSET_LINE_ENABLE) {
                oa->windowCoord.z -= zOffset;
                ob->windowCoord.z -= zOffset;
                oc->windowCoord.z -= zOffset;
            }

            break;

        default:
            break;
    }

    // Restore original colors if needed:

    if (pRc->privateEnables & __MCDENABLE_SMOOTH) {

        if (backFace) {

            SWAP_COLOR(a);
            SWAP_COLOR(b);
            SWAP_COLOR(c);
        }
    } else { // Flat shading

        if (polygonMode == GL_FILL) {
            if (backFace) {
                SWAP_COLOR(pv);
            }
        } else {

            if (backFace) {
                SWAP_COLOR(pv);
            }

            RESTORE_COLOR(tempA, a);
            RESTORE_COLOR(tempB, b);
            RESTORE_COLOR(tempC, c);
        }
    }
}


////////////////////////////////////////////////////////////////////////
//
// VOID FASTCALL __MCDRenderFlatTriangle(DEVRC *pRc, MCDVERTEX *a,
//                                       MCDVERTEX *b, MCDVERTEX *c)
//
//
// This is the top-level flat-shaded triangle renderer.
//
////////////////////////////////////////////////////////////////////////

VOID FASTCALL __MCDRenderFlatTriangle(DEVRC *pRc, MCDVERTEX *a, MCDVERTEX *b,
                                      MCDVERTEX *c)
{
    LONG ccw, face;
    RECTL *pClip;
    ULONG clipNum;

//!!    MCDBG_PRINT("__MCDRenderFlatTriangle");

    SORT_AND_CULL_FACE(a, b, c, face, ccw);
    if (CASTINT(pRc->halfArea) == 0)
        return;

    if ((clipNum = pRc->pEnumClip->c) > 1) {
        pClip = &pRc->pEnumClip->arcl[0];
        (*pRc->HWSetupClipRect)(pRc, pClip++);
    }

    // Pick correct face color and render the triangle:

    if ((pRc->privateEnables & __MCDENABLE_TWOSIDED) &&
        (face == __MCD_BACKFACE))
    {
        MCDVERTEX *pv = pRc->pvProvoking;

        SWAP_COLOR(pv);

        (*pRc->drawTri)(pRc, a, b, c, (BOOL) ccw);
        while (--clipNum) {
            (*pRc->HWSetupClipRect)(pRc, pClip++);
            (*pRc->drawTri)(pRc, a, b, c, (BOOL) ccw);
        }

        SWAP_COLOR(pv);
    }
    else
    {
        (*pRc->drawTri)(pRc, a, b, c, (BOOL) ccw);
        while (--clipNum) {
            (*pRc->HWSetupClipRect)(pRc, pClip++);
            (*pRc->drawTri)(pRc, a, b, c, (BOOL) ccw);
        }
    }
}


////////////////////////////////////////////////////////////////////////
//
// VOID FASTCALL __MCDRenderSmoothTriangle(DEVRC *pRc, MCDVERTEX *a,
//                                         MCDVERTEX *b, MCDVERTEX *c)
//
//
// This is the top-level smooth triangle renderer.
//
////////////////////////////////////////////////////////////////////////

VOID FASTCALL __MCDRenderSmoothTriangle(DEVRC *pRc, MCDVERTEX *a, MCDVERTEX *b,
                                        MCDVERTEX *c)
{
    LONG ccw, face;
    RECTL *pClip;
    ULONG clipNum;

//!!    MCDBG_PRINT("__MCDRenderSmoothTriangle");

    SORT_AND_CULL_FACE(a, b, c, face, ccw);
    if (CASTINT(pRc->halfArea) == 0)
        return;

    if ((clipNum = pRc->pEnumClip->c) > 1) {
        pClip = &pRc->pEnumClip->arcl[0];
        (*pRc->HWSetupClipRect)(pRc, pClip++);
    }

    // Pick correct face color and render the triangle:

    if ((pRc->privateEnables & __MCDENABLE_TWOSIDED) &&
        (face == __MCD_BACKFACE))
    {
        SWAP_COLOR(a);
        SWAP_COLOR(b);
        SWAP_COLOR(c);

        (*pRc->drawTri)(pRc, a, b, c, (BOOL) ccw);
        while (--clipNum) {
            (*pRc->HWSetupClipRect)(pRc, pClip++);
            (*pRc->drawTri)(pRc, a, b, c, (BOOL) ccw);
        }

        SWAP_COLOR(a);
        SWAP_COLOR(b);
        SWAP_COLOR(c);
    }
    else
    {
        (*pRc->drawTri)(pRc, a, b, c, (BOOL) ccw);
        while (--clipNum) {
            (*pRc->HWSetupClipRect)(pRc, pClip++);
            (*pRc->drawTri)(pRc, a, b, c, (BOOL) ccw);
        }
    }
}


VOID FASTCALL __MCDRenderFlatFogTriangle(DEVRC *pRc, MCDVERTEX *pv1, MCDVERTEX *pv2,
                                         MCDVERTEX *pv3)
{
    MCDCOLOR c1, c2, c3;
    MCDCOLOR bc1, bc2, bc3;
    MCDCOLOR cProvoking;

    c1 = pv1->colors[0];
    c2 = pv2->colors[0];
    c3 = pv3->colors[0];

    // We have to save a copy of the provoking color since we
    // can overwrite it!

    cProvoking = pRc->pvProvoking->colors[0];

    __MCDCalcFogColor(pRc, pv1, &pv1->colors[0], &cProvoking);
    __MCDCalcFogColor(pRc, pv2, &pv2->colors[0], &cProvoking);
    __MCDCalcFogColor(pRc, pv3, &pv3->colors[0], &cProvoking);

    if (pRc->privateEnables & __MCDENABLE_TWOSIDED) {
        cProvoking = pRc->pvProvoking->colors[1];
        bc1 = pv1->colors[1];
        bc2 = pv2->colors[1];
        bc3 = pv3->colors[1];
        __MCDCalcFogColor(pRc, pv1, &pv1->colors[1], &cProvoking);
        __MCDCalcFogColor(pRc, pv2, &pv2->colors[1], &cProvoking);
        __MCDCalcFogColor(pRc, pv3, &pv3->colors[1], &cProvoking);
    }

    (*pRc->renderTriX)(pRc, pv1, pv2, pv3);

    pv1->colors[0] = c1;
    pv2->colors[0] = c2;
    pv3->colors[0] = c3;
    if (pRc->privateEnables & __MCDENABLE_TWOSIDED) {
        pv1->colors[1] = bc1;
        pv2->colors[1] = bc2;
        pv3->colors[1] = bc3;
    }
}

