/* @doc 
 *
 * Lighting pipeline components
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/****************************************************************************
 Includes
 */

#include <stdlib.h>
#include <math.h>
#include <string.h>

#include <rwcore.h>

#include "balight.h"
#include "baworld.h"
#include "litepipe.h"

/****************************************************************************
 Local Types
 */

/****************************************************************************
 Local (Static) Prototypes
 */

static void     _rwApplyAmbientLight(RwResEntry * repEntry, const void *voidLight,
                                     const RwMatrix * inverseMat,
                                     RwReal invScale,
                                     const RwSurfaceProperties * surfaceProps);
static void     _rwApplyDirectionalLight(RwResEntry * repEntry, const void *voidLight,
                                         const RwMatrix * inverseMat,
                                         RwReal invScale,
                                         const RwSurfaceProperties * surfaceProps);
static void     _rwApplyPointLight(RwResEntry * repEntry, const void *voidLight,
                                   const RwMatrix * inverseMat, RwReal invScale,
                                   const RwSurfaceProperties * surfaceProps);
static void     _rwApplySpotLight(RwResEntry * repEntry, const void *voidLight,
                                  const RwMatrix * inverseMat, RwReal invScale,
                                  const RwSurfaceProperties * surfaceProps);
static void     _rwApplySpotSoftLight(RwResEntry * repEntry, const void *voidLight,
                                      const RwMatrix * inverseMat,
                                      RwReal invScale,
                                      const RwSurfaceProperties * surfaceProps);

/****************************************************************************
 Local Defines
 */

#define CLAMPCOLCOMPONENT(color,component)           \
    if (*(RwUInt32 *)&((color)->component) >         \
        *(const RwUInt32 *)&maxlum)                  \
    {                                                \
        /* We need to clamp red somehow */           \
        if (*(RwInt32 *)&((color)->component) < 0)   \
        {                                            \
            *(RwInt32 *)&((color)->component) = 0;   \
        }                                            \
        else                                         \
        {                                            \
            *(RwInt32 *)&((color)->component) =      \
                *(const RwInt32 *)&maxlum;           \
        }                                            \
    }

/****************************************************************************
 Globals (across program)
 */

/****************************************************************************
 Local (static) Globals
 */

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                         Opening and closing

 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

/***************************************************************************
 _rpPipeApplyLight

 On entry   : Instanced data
            : Light
            : Optional inverse object matrix (to transform light to object space)
            : Inverse scale of object
            : Surface properties of the light
 On exit    :
 */

void
_rpPipeApplyLight(RwResEntry * repEntry, const void *voidLight, const
                  RwMatrix * inverseMat,
                  RwReal invScale, const RwSurfaceProperties * surfaceProps)
{
    RWFUNCTION(RWSTRING("_rpPipeApplyLight"));
    RWASSERT(voidLight);
    RWASSERTISTYPE(voidLight, rpLIGHT);
    RWASSERT(surfaceProps);

    /* Only apply lights if something is in the viewport */
    if (_rwPipeState.currentContext->clipFlagsAnd == 0)
    {
        const RpLight  *light = (const RpLight *) voidLight;

        /* Now we have applied one */
        RWPOLYHEADER(repEntry)->pipelineOpFlags |= rwLIGHTAPPLIED;

        light->applyLight(repEntry, voidLight, inverseMat, invScale, surfaceProps);
    }

    RWRETURNVOID();
}

/***************************************************************************
 _rpPipePreLight

 On entry   : Instanced data
 On exit    : none
 */

void
_rpPipePreLight(RwResEntry * repEntry)
{
    RWFUNCTION(RWSTRING("_rpPipePreLight"));

    /* Only bother if something is in the viewport */
    if (_rwPipeState.currentContext->clipFlagsAnd == 0)
    {
        RwCameraVertex *cameraVertex = RwCameraVertexGet(0);
        RWVERTEXINSTANCE *instancedVertex =
        RWVERTEXINSTANCEGet(repEntry, 0);
        RwInt32         nNumVert = RWVERTEXINSTANCEGetQuantity(repEntry);

        while (nNumVert--)
        {
            RwRGBA         *rgb = RWVERTEXINSTANCEGetPreLitRGBA(instancedVertex);

            RwCameraVertexSetRGBA(cameraVertex,
                                  rgb->red, rgb->green, rgb->blue,
                                  rgb->alpha);

            cameraVertex = RwCameraVertexGetNext(cameraVertex);
            instancedVertex = RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);
        }
    }

    RWRETURNVOID();
}

/***************************************************************************
 _rpPipePostLight

 On entry   : Instanced data
 On exit    : none
 */

void
_rpPipePostLight(RwResEntry * repEntry)
{
    RWFUNCTION(RWSTRING("_rpPipePostLight"));

    /* Only bother if something is in the viewport */
    if (_rwPipeState.currentContext->clipFlagsAnd == 0)
    {
        RwCameraVertex *cameraVertex = RwCameraVertexGet(0);
        RwInt32         nNumVert = RWVERTEXINSTANCEGetQuantity(repEntry);
        const RwReal    maxlum = (RwReal) ((255.0));

        RwInstDataPolyHeader *polyHeader = RWPOLYHEADER(repEntry);

        /* If all the meshes are coloured by an opaque white material (which is
         * * likely), we can optimise the render stage by copying lighting values
         * * from the camera vertices into the device vertices here.
         */

        if (!(polyHeader->flags & rpWORLDMODULATEMATERIALCOLOR))
        {
            RwIm2DVertex   *deviceVertex = RwIm2DVertexGet(0);

            /* If we applied a light, probably best to clamp the values */
            if (polyHeader->pipelineOpFlags & rwLIGHTAPPLIED)
            {
                /* Clamp 'n' copy them */
                while (nNumVert--)
                {
                    RwRGBAReal     *const col = RwCameraVertexGetRGBA(cameraVertex);

                    /* Assume that things are not going to be clamped here */
                    CLAMPCOLCOMPONENT(col, red);
                    CLAMPCOLCOMPONENT(col, green);
                    CLAMPCOLCOMPONENT(col, blue);

                    RwIm2DVertexSetRealRGBA(deviceVertex, col->red,
                                            col->green, col->blue, col->alpha);

                    cameraVertex = RwCameraVertexGetNext(cameraVertex);
                    deviceVertex = RwIm2DVertexGetNext(deviceVertex);
                }
            }
            else
            {
                /* Just copy them */
                while (nNumVert--)
                {
                    RwRGBAReal     *const col = RwCameraVertexGetRGBA(cameraVertex);

                    RwIm2DVertexSetRealRGBA(deviceVertex, col->red,
                                            col->green, col->blue, col->alpha);

                    cameraVertex = RwCameraVertexGetNext(cameraVertex);
                    deviceVertex = RwIm2DVertexGetNext(deviceVertex);
                }
            }
        }
        else
        {
            /* If we applied a light, probably best to clamp the values */
            if (polyHeader->pipelineOpFlags & rwLIGHTAPPLIED)
            {
                /* Just clamp them */
                while (nNumVert--)
                {
                    RwRGBAReal     *const col = RwCameraVertexGetRGBA(cameraVertex);

                    /* Assume that things are not going to be clamped here */
                    CLAMPCOLCOMPONENT(col, red);
                    CLAMPCOLCOMPONENT(col, green);
                    CLAMPCOLCOMPONENT(col, blue);

                    cameraVertex = RwCameraVertexGetNext(cameraVertex);
                }
            }
        }
    }

    RWRETURNVOID();
}

/***************************************************************************
 initGenericLightApply

 On entry   : Light
 On exit    : none
 */

void 
initGenericLightApply(RpLight *light)
{
    RWFUNCTION(RWSTRING("initGenericLightApply"));
    RWASSERT(light);

    switch (RpLightGetType(light))
    {
        case (rpLIGHTAMBIENT):
        {
            light->applyLight = _rwApplyAmbientLight;
            break;
        }
        case (rpLIGHTDIRECTIONAL):
        {
            light->applyLight = _rwApplyDirectionalLight;
            break;
        }
        case (rpLIGHTPOINT):
        {
            light->applyLight = _rwApplyPointLight;
            break;
        }
        case (rpLIGHTSPOT):
        {
            light->applyLight = _rwApplySpotLight;
            break;
        }
        case (rpLIGHTSPOTSOFT):
        {
            light->applyLight = _rwApplySpotSoftLight;
            break;
        }
        default:
        {
            /* Just take the unknown case set up in balight.c */
            break;
        }
    }

    RWRETURNVOID();
}

/***************************************************************************/

#define VERTSARG RwResEntry *repEntry

#define OBJCAMVERTDECL RWVERTEXINSTANCE *instancedVertex; \
                       RwCameraVertex *cameraVertex

#define CAMVERTDECL RwCameraVertex *cameraVertex
#define NUMVERTDECL RwInt32 numVert

#define OBJCAMVERTINIT()                                        \
{                                                               \
    instancedVertex = RWVERTEXINSTANCEGet(repEntry, 0);         \
    cameraVertex    = RwCameraVertexGet(0);                     \
}

#define CAMVERTINIT()                           \
{                                               \
    cameraVertex = RwCameraVertexGet(0);        \
}

#define NUMVERTINIT()                                   \
{                                                       \
    numVert = RWVERTEXINSTANCEGetQuantity(repEntry);    \
}

#define OBJCAMVERTINC()                                                         \
{                                                                               \
    instancedVertex = RWVERTEXINSTANCEGetNext(repEntry, instancedVertex);       \
    cameraVertex    = RwCameraVertexGetNext(cameraVertex);                      \
}

#define CAMVERTINC()                                            \
{                                                               \
    cameraVertex = RwCameraVertexGetNext(cameraVertex);         \
}

#define OBJVERTGETNORMAL(vector)                              \
    (  *vector = *RWVERTEXINSTANCEGetNormal(instancedVertex) ) 

#define OBJVERTGETPOS(vector)                                 \
    (   *vector = *RWVERTEXINSTANCEGetPos(instancedVertex) )

#define CAMVERTADDRGBA(r, g, b, a)                      \
    RwCameraVertexAddRGBA(cameraVertex, (r*lum), (g*lum), (b*lum), (a*lum))

#include "baaplylt.c"

static const char rcsid[] __RWUNUSED__ = "@@(#)$Id: litepipe.c,v 1.12 2000/11/22 13:05:24 iestynb Exp $";

