/*
 * Refinement plugin
 */

/****************************************************************************
 *                                                                          *
 *  Module  :   nodeRefine.c                                                *
 *                                                                          *
 *  Purpose :   Node of Surface Refinement plugin (rpRefine.c)              *
 *                                                                          *
 ****************************************************************************/

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

#include "rpplugin.h"
#include "time.h"
#include <rpdbgerr.h>
#include <rwcore.h>
#include <rpworld.h>

#include "bbtptool.h"
#include "rprefine.h"
#include "refinevars.h"

#include "nodeRefine.h"

#if (!defined(DOXYGEN_SHOULD_SKIP_THIS))
static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: nodeRefine.c,v 1.77 2001/02/02 16:04:18 johns Exp $";
#endif /* (!defined(DOXYGEN_SHOULD_SKIP_THIS)) */

/****************************************************************************
 Local defines
 */

#define NUMCLUSTERSOFINTEREST 6
#define NUMOUTPUTS            2

#define MESSAGE(_string)                                     \
    RwDebugSendMessage(rwDEBUGMESSAGE, "RefineCSL", _string)

#define _BARYCENTRICINTERPOLATION( _out, _t1, _t2, _t3, _in1, _in2, _in3 ) \
    (_out) = ((_in1) * (_t1)) + ((_in2) + (_t2)) + ((_in3) * (_t3))

#define REFINEOUT         0
#define REFINEPASSTHROUGH 1

/*****************************************************************************
                           Utilities
 ****************************************************************************/

#if (!defined(__RWINLINE__))
#define __RWINLINE__           /* __inline */
#endif /* (!defined(__RWINLINE__)) */

#define _baryCentricInterpolation(_res, _t1, _t2, _t3, _vpV1, _vpV2, _vpV3) \
MACRO_START                                                                 \
{                                                                           \
    (_res)->x = (_vpV1)->x * (_t1) +                                        \
                (_vpV2)->x * (_t2) +                                        \
                (_vpV3)->x * (_t3);                                         \
    (_res)->y = (_vpV1)->y * (_t1) +                                        \
                (_vpV2)->y * (_t2) +                                        \
                (_vpV3)->y * (_t3);                                         \
    (_res)->z = (_vpV1)->z * (_t1) +                                        \
                (_vpV2)->z * (_t2) +                                        \
                (_vpV3)->z * (_t3);                                         \
}                                                                           \
MACRO_STOP

#define _baryCentricInterpolationUV(_ST, _t1, _t2, _t3, _ss, _tt)       \
MACRO_START                                                             \
{                                                                       \
    RwReal  _r1 = _t1,  _r2 = _t2, _r3 = _t3;                           \
    RwReal *_uu = _ss, *_vv = _tt;                                      \
    RxUV *_UV = _ST;                                                  \
                                                                        \
    _UV->u = _r1*_uu[0] + _r2*_uu[1] + _r3*_uu[2];                      \
    _UV->v = _r1*_vv[0] + _r2*_vv[1] + _r3*_vv[2];                      \
}                                                                       \
MACRO_STOP

#define _interpolateExtraUVs(_UVs, _t1, _t2, _t3, _ss, _tt)             \
MACRO_START                                                             \
{                                                                       \
    _baryCentricInterpolationUV(RxClusterGetCursorData(_UVs, RxUV),            \
                                _t1, _t2, _t3, _ss, _tt);               \
    RxClusterIncCursor(_UVs);                                              \
}                                                                       \
MACRO_STOP

/*
#define _interpolateExtraRGBAs(_RGBAs, _t1, _t2, _t3, _col)             \
MACRO_START                                                             \
{                                                                       \
    RwUInt32  _r1 = RwFastRealToUInt32((_t1)*4194304.0f),               \
              _r2 = RwFastRealToUInt32((_t2)*4194304.0f),               \
              _r3 = RwFastRealToUInt32((_t3)*4194304.0f);               \
    RwRGBA *_rgba = _col, rgbaTmp;                                      \
                                                                        \
    rgbaTmp.red   = (RwUInt8) ((_r1*(RwUInt32)_rgba[0].red   +          \
                                _r2*(RwUInt32)_rgba[1].red   +          \
                                _r3*(RwUInt32)_rgba[2].red)   >> 22);   \
    rgbaTmp.green = (RwUInt8) ((_r1*(RwUInt32)_rgba[0].green +          \
                                _r2*(RwUInt32)_rgba[1].green +          \
                                _r3*(RwUInt32)_rgba[2].green) >> 22);   \
    rgbaTmp.blue  = (RwUInt8) ((_r1*(RwUInt32)_rgba[0].blue  +          \
                                _r2*(RwUInt32)_rgba[1].blue  +          \
                                _r3*(RwUInt32)_rgba[2].blue)  >> 22);   \
    rgbaTmp.alpha = (RwUInt8) ((_r1*(RwUInt32)_rgba[0].alpha +          \
                                _r2*(RwUInt32)_rgba[1].alpha +          \
                                _r3*(RwUInt32)_rgba[2].alpha) >> 22);   \
   *RxClusterGetCursorData(  _RGBAs, RwRGBA) = rgbaTmp;                          \
    RxClusterIncCursor(_RGBAs);                                            \
}                                                                       \
MACRO_STOP
*/

#define _baryCentricInterpolationCol(_col, _t1, _t2, _t3, _cols)        \
MACRO_START                                                             \
{                                                                       \
    RwReal      _r1   = _t1,   _r2    = _t2, _r3 = _t3;                 \
    RwRGBAReal *_rgba = _col, *_rgbas = _cols;                          \
                                                                        \
    _rgba->red  =                                                       \
        _rgbas[0].red  *_r1 +                                           \
        _rgbas[1].red  *_r2 +                                           \
        _rgbas[2].red  *_r3;                                            \
    _rgba->green=                                                       \
        _rgbas[0].green*_r1 +                                           \
        _rgbas[1].green*_r2 +                                           \
        _rgbas[2].green*_r3;                                            \
    _rgba->blue =                                                       \
        _rgbas[0].blue *_r1 +                                           \
        _rgbas[1].blue *_r2 +                                           \
        _rgbas[2].blue *_r3;                                            \
    _rgba->alpha=                                                       \
        _rgbas[0].alpha*_r1 +                                           \
        _rgbas[1].alpha*_r2 +                                           \
        _rgbas[2].alpha*_r3;                                            \
}                                                                       \
MACRO_STOP

#define _interpolateExtraRGBAs(_RGBAs, _t1, _t2, _t3, _cols)            \
MACRO_START                                                             \
{                                                                       \
    _baryCentricInterpolationCol(RxClusterGetCursorData(_RGBAs, RwRGBAReal),     \
                                 _t1, _t2, _t3, _cols);                 \
    RxClusterIncCursor(_RGBAs);                                            \
}                                                                       \
MACRO_STOP

/*****************************************************************************
 * Generate the refinememt.
 */
static void
_refineProjectVerts(refineRefineData * __RWUNUSED__ refineData,
                    refineCameraData * cameraData,
                    RxCamSpace3DVertex * cv, RxScrSpace2DVertex * dv)
{
    RwReal              outx, outy, outz, nRecipZ;
    RwV3d               screen;

    RWFUNCTION(RWSTRING("_refineProjectVerts"));

    screen = cv->cameraVertex;
    outx = screen.x;
    outy = screen.y;
    outz = screen.z;
    nRecipZ = (((RwReal) (1)) / (outz));

    /* Set the recip */
    RwIm2DVertexSetCameraX(dv, outx);
    RwIm2DVertexSetCameraY(dv, outy);
    RwIm2DVertexSetCameraZ(dv, outz);
    RwIm2DVertexSetRecipCameraZ(dv, nRecipZ);

    /* Perspective projection */
    RwIm2DVertexSetScreenX(dv,
                           ((((outx) * (nRecipZ))) *
                            (cameraData->camWidth)) +
                           cameraData->camOffsetX);
    RwIm2DVertexSetScreenY(dv,
                           ((((outy) * (nRecipZ))) *
                            (cameraData->camHeight)) +
                           cameraData->camOffsetY);
    RwIm2DVertexSetScreenZ(dv,
                           ((cameraData->zScale) * (nRecipZ)) +
                           cameraData->zShift);

    /* from bapiptrn.c */
    cv->clipFlags =
        ((outz < cameraData->nearClip) ?
         rwZLOCLIP :
         ((outz > cameraData->farClip) ?
          rwZHICLIP :
          0)) | ((outy < (RwReal) (0)) ?
                 rwYLOCLIP :
                 ((outy > outz) ?
                  rwYHICLIP :
                  0)) | ((outx < (RwReal) (0)) ?
                         rwXLOCLIP : ((outx > outz) ? rwXHICLIP : 0));
    /* and texture */
    RwIm2DVertexSetU(dv, cv->u, nRecipZ);
    RwIm2DVertexSetV(dv, cv->v, nRecipZ);

    RWRETURNVOID();
}

/*****************************************************************************
 * Generate the refinememt.
 */
static              RwBool
_refineGenerateRefinement(refineRefineData * refineData,
                          refineCameraData * cameraData, RxHeap * heap)
{
    RwInt32             i, j, k, d, numTris, oldNumVerts;
    RxCluster          *idxs, *objVerts, *camNorms, *camVerts,
        *devVerts;
    RwReal              recip;

    RwUInt32            numPresentUVs, numPresentRGBAs;
    RxCluster         **extraUVs, **extraRGBAs;

    RwV3d              *cn;
    RxScrSpace2DVertex *dv;
    RxCamSpace3DVertex *cv;

    RwUInt32            uv, rgba;

    /*RwRGBA *extraCol; */
    RwRGBAReal         *extraCol = (RwRGBAReal *)NULL;
    RwReal             *extraSS = (RwReal *)NULL;
    RwReal             *extraTT = (RwReal *)NULL;

#if (0)
    RwReal              outx, outy, outz, nRecipZ;
    RwV3d               screen;
#endif /* (0) */

    RWFUNCTION(RWSTRING("_refineGenerateRefinement"));

    numPresentUVs = refineData->numPresentUVs;
    numPresentRGBAs = refineData->numPresentRGBAs;

    extraUVs = refineData->extraUVs;
    extraRGBAs = refineData->extraRGBAs;

    if (numPresentUVs > 0)
    {
        extraSS = (RwReal *)
            RxHeapAlloc(heap, 2 * (3 * numPresentUVs * sizeof(RwReal)));
        RWASSERT(NULL != extraSS);

        extraTT = (RwReal *)
            (RwReal *) (((RwUInt8 *) extraSS) +
                        3 * numPresentUVs * sizeof(RwReal));
    }
    if (numPresentRGBAs > 0)
    {
        /* extraCol = RxHeapAlloc(heap, (3*numPresentRGBAs*sizeof(RwRGBA))); */
        extraCol = (RwRGBAReal *)
            RxHeapAlloc(heap,
                        (3 * numPresentRGBAs * sizeof(RwRGBAReal)));
        RWASSERT(NULL != extraCol);
    }

    numTris = refineData->oldNumTris;
    oldNumVerts = refineData->oldNumVerts;
    d = refineData->depth;
    recip = (RwReal) 1.0 / (RwReal) d;

    idxs = refineData->idxs;
    objVerts = refineData->objVerts;
    camNorms = refineData->camNorms;
    camVerts = refineData->camVerts;
    devVerts = refineData->devVerts;

    cn = RxClusterGetIndexedData(camNorms, RwV3d, oldNumVerts);
    cv = RxClusterGetIndexedData(camVerts, RxCamSpace3DVertex,
                                 oldNumVerts);
    dv = RxClusterGetIndexedData(devVerts, RxScrSpace2DVertex,
                                 oldNumVerts);
    for (uv = 0; uv < numPresentUVs; uv++)
    {
        extraUVs[uv]->currentData =
            RxClusterGetIndexedData(extraUVs[uv], void, oldNumVerts);
        RWASSERT(NULL != extraUVs[uv]->currentData);
    }
    for (rgba = 0; rgba < numPresentRGBAs; rgba++)
    {
        extraRGBAs[rgba]->currentData =
            RxClusterGetIndexedData(extraRGBAs[rgba], void,
                                    oldNumVerts);
        RWASSERT(NULL != extraRGBAs[rgba]->currentData);
    }

    for (k = 0; k < numTris; k++)
    {
        RwV3d               n[3], v[3];
        BBTPOrdinates       ords;
        RwRGBAReal          col[3];
        RwReal              ss[3], tt[3];
        RxVertexIndex      *t;

        t = RxClusterGetIndexedData(idxs, RxVertexIndex, k * 3);

        /* copy across the vertex data, then generate ords */
        RxObjSpace3DVertexGetPos(RxClusterGetIndexedData
                                 (objVerts, RxObjSpace3DVertex, t[0]),
                                 &v[0]);
        RxObjSpace3DVertexGetNormal(RxClusterGetIndexedData
                                    (objVerts, RxObjSpace3DVertex,
                                     t[0]), &n[0]);

        RxObjSpace3DVertexGetPos(RxClusterGetIndexedData
                                 (objVerts, RxObjSpace3DVertex, t[1]),
                                 &v[1]);
        RxObjSpace3DVertexGetNormal(RxClusterGetIndexedData
                                    (objVerts, RxObjSpace3DVertex,
                                     t[1]), &n[1]);

        RxObjSpace3DVertexGetPos(RxClusterGetIndexedData
                                 (objVerts, RxObjSpace3DVertex, t[2]),
                                 &v[2]);
        RxObjSpace3DVertexGetNormal(RxClusterGetIndexedData
                                    (objVerts, RxObjSpace3DVertex,
                                     t[2]), &n[2]);

        _rtbbtpGenerateOrdinates(&ords, v, n);

        /*
         * copy across the camera space vertex data,
         * colours and texture coords
         */
        for (j = 0; j < 3; j++)
        {
            RxVertexIndex       index = t[j];
            RxScrSpace2DVertex *dvtmp;

            v[j] =
                RxClusterGetIndexedData(camVerts, RxCamSpace3DVertex,
                                        index)->cameraVertex;
            n[j] = *RxClusterGetIndexedData(camNorms, RwV3d, index);
            dvtmp =
                RxClusterGetIndexedData(devVerts, RxScrSpace2DVertex,
                                        index);
            col[j].red = (RwReal) RwIm2DVertexGetRed(dvtmp);
            col[j].green = (RwReal) RwIm2DVertexGetGreen(dvtmp);
            col[j].blue = (RwReal) RwIm2DVertexGetBlue(dvtmp);
            col[j].alpha = (RwReal) RwIm2DVertexGetAlpha(dvtmp);
            ss[j] =
                RxClusterGetIndexedData(camVerts, RxCamSpace3DVertex,
                                        index)->u;
            tt[j] =
                RxClusterGetIndexedData(camVerts, RxCamSpace3DVertex,
                                        index)->v;
            /* Get extra UVs/RGBAs for the corners */
            for (uv = 0; uv < numPresentUVs; uv++)
            {
                RxUV               *curUV =
                    RxClusterGetIndexedData(extraUVs[uv], RxUV, index);

                extraSS[uv * 3 + j] = curUV->u;
                extraTT[uv * 3 + j] = curUV->v;
            }
            for (rgba = 0; rgba < numPresentRGBAs; rgba++)
            {
                /*
                 * extraCol[rgba*3 + j] =
                 * *RxClusterGetIndexedData(extraRGBAs[rgba], RwRGBA, index);
                 */

                extraCol[rgba * 3 + j] =
                    *RxClusterGetIndexedData(extraRGBAs[rgba],
                                             RwRGBAReal, index);
            }
        }

        for (i = 0; i <= d; i++)
        {
            RwV3d               tempV, tempN;
            RwInt32             l1, l2, l3; /* multi-indices */
            RwReal              t1, t2, t3; /* barycentric coords */

            l1 = i;
            t1 = recip * l1;
            for (j = 0; j <= d - i; j++)
            {
                l2 = d - i - j;
                l3 = d - l1 - l2;

                if ((l1 == d) || (l2 == d) || (l3 == d))
                {
                    /* vertices, nothing to do */
                }
                else
                {
                    RxUV                uvTmp;
                    RwRGBAReal          tempCol;
                    RwReal              height;

                    /* compute barycentric coords */
                    t2 = recip * l2;
                    /*t3 = recip * l3; */
                    t3 = 1 - (t1 + t2);

                    _baryCentricInterpolation(&tempN,
                                              t2, t1, t3, n + 0, n + 1,
                                              n + 2);
                    *cn = tempN;

                    height = _rtbbtpPatchEvaluate(&ords, t2, t1, t3);
                    _baryCentricInterpolation(&tempV,
                                              t2, t1, t3, v + 0, v + 1,
                                              v + 2);

                    tempN.x *= height;
                    tempN.y *= height;
                    tempN.z *= height;
                    tempV.x += tempN.x;
                    tempV.y += tempN.y;
                    tempV.z += tempN.z;
                    cv->cameraVertex = tempV;

                    /* Interpolate RGBAs */

                    tempCol.red =
                        t2 * col[0].red + t1 * col[1].red +
                        t3 * col[2].red;
                    tempCol.green =
                        t2 * col[0].green + t1 * col[1].green +
                        t3 * col[2].green;
                    tempCol.blue =
                        t2 * col[0].blue + t1 * col[1].blue +
                        t3 * col[2].blue;
                    tempCol.alpha =
                        t2 * col[0].alpha + t1 * col[1].alpha +
                        t3 * col[2].alpha;
                    _baryCentricInterpolationCol(&tempCol, t2, t1, t3,
                                                 col);

                    RwIm2DVertexSetRealRGBA(dv, tempCol.red,
                                            tempCol.green, tempCol.blue,
                                            tempCol.alpha);

                    /* Interpolate whatever extra RGBAs there may be... */
                    for (rgba = 0; rgba < numPresentRGBAs; rgba++)
                    {
                        _interpolateExtraRGBAs(extraRGBAs[rgba], t2, t1,
                                               t3, &extraCol[3 * rgba]);
                    }

                    /* Interpolate UVs */

                    /*
                     * RwReal s, t;
                     * s = t2*ss[0]+t1*ss[1]+t3*ss[2];
                     * t = t2*tt[0]+t1*tt[1]+t3*tt[2];
                     * cv->u = s;
                     * cv->v = t;
                     */

                    _baryCentricInterpolationUV(&uvTmp, t2, t1, t3, ss,
                                                tt);
                    cv->u = uvTmp.u;
                    cv->v = uvTmp.v;

                    /* Interpolate whatever extra UVs there may be... */
                    for (uv = 0; uv < numPresentUVs; uv++)
                    {
                        _interpolateExtraUVs(extraUVs[uv], t2, t1, t3,
                                             &extraSS[3 * uv],
                                             &extraTT[3 * uv]);
                    }

                    /* Project the verts. */

                    _refineProjectVerts(refineData, cameraData, cv, dv);

                    cv++;
                    cn++;
                    dv++;
                }
            }
        }
    }

    if (extraSS != NULL)
        RxHeapFree(heap, extraSS);
    if (extraCol != NULL)
        RxHeapFree(heap, extraCol);

    RWRETURN(TRUE);
}

RwBool
_rtrefineSetupCamera(refineCameraData * cameraData)
{
    RwBool              result;
    RwCamera           *camera = (RwCamera *) RWSRCGLOBAL(curCamera);

    RWFUNCTION(RWSTRING("_rtrefineSetupCamera"));

    result = FALSE;

    if (camera)
    {
        RwRaster           *camRaster = RwCameraGetRaster(camera);
        RwInt32             camWidth;
        RwInt32             camHeight;

        cameraData->zScale = camera->zScale;
        cameraData->zShift = camera->zShift;
        camWidth = RwRasterGetWidth(camRaster);
        cameraData->camWidth = (RwReal) camWidth;
        camHeight = RwRasterGetHeight(camRaster);
        cameraData->camHeight = (RwReal) camHeight;
        cameraData->camOffsetX = (RwReal) camRaster->nOffsetX;
        cameraData->camOffsetY = (RwReal) camRaster->nOffsetY;
        cameraData->nearClip = camera->nearPlane;
        cameraData->farClip = camera->farPlane;

        result = TRUE;
    }

    RWRETURN(result);
}

#if (defined(RWDEBUG))
#define DEBUGREFINEGENERATENEWVERTINDEX(_start, _vis)  \
do                                                     \
{                                                      \
    static char         s[100];                        \
                                                       \
    sprintf(s, "%hd %hd %hd\n",                        \
            (int)((_start) + (_vis)[0]),               \
            (int)((_start) + (_vis)[1]),               \
            (int)((_start) + (_vis)[2]));              \
    /*PiDisplayMessage(s); */                          \
}   while (0)
#endif /* (defined(RWDEBUG)) */

#if (!defined(DEBUGREFINEGENERATENEWVERTINDEX))
#define DEBUGREFINEGENERATENEWVERTINDEX(_start, _vis) /* No Op */
#endif /* (!defined(DEBUGREFINEGENERATENEWVERTINDEX)) */

RwBool
_rtrefineGenerateNewVertIndex(refineRefineData * refineData)
{
    RwInt32             i, j, k, numVerts, numTris, depth;
    RwInt32            *indexArray;
    RxCluster          *idxs;
    RxVertexIndex      *indexPtr;

    RWFUNCTION(RWSTRING("_rtrefineGenerateNewVertIndex"));

    numTris = refineData->oldNumTris;
    numVerts = refineData->oldNumVerts;
    depth = refineData->depth;
    idxs = refineData->idxs;

    indexArray = _rpRefineGetVertIndexArray(depth);

    /* getting there, finally generate new indices */
    for (i = 0; i < numTris; i++)
    {
        RwInt32             index[3];
        RwInt32             start;
        RwInt32            *vis = indexArray;

        j = (numTris - 1) - i;
        k = j * depth * depth;

        indexPtr = RxClusterGetIndexedData(idxs, RxVertexIndex, j * 3);
        index[0] = (RxVertexIndex) indexPtr[0];
        index[1] = (RxVertexIndex) indexPtr[1];
        index[2] = (RxVertexIndex) indexPtr[2];

        start =
            (RwInt32) (numVerts +
                       j * ((((depth + 1) * (depth + 2)) >> 1) - 3));

        /* emit the triangle indices */

        vis[1] = index[0] - start;
        vis[((depth - 1) * 2) * 3] = index[2] - start;
        vis[((depth * depth) * 3) - 1] = index[1] - start;

        for (j = 0; j < depth * depth; j++, k++, vis += 3)
        {
            indexPtr =
                RxClusterGetIndexedData(idxs, RxVertexIndex, k * 3);

            indexPtr[0] = (RxVertexIndex) (start + vis[0]);
            indexPtr[1] = (RxVertexIndex) (start + vis[1]);
            indexPtr[2] = (RxVertexIndex) (start + vis[2]);

            DEBUGREFINEGENERATENEWVERTINDEX(start, vis);
        }
    }

    RWRETURN(TRUE);
}

/*****************************************************************************
                           Node core funcs
 ****************************************************************************/

#if (defined(RWDEBUG))
#define DEBUGREFINEPROCESSPACKETUV(_i)                                             \
do                                                                                 \
{                                                                                  \
    static char                string[256];                                        \
                                                                                   \
    sprintf(string,                                                                \
            "UVs cluster %d does not coincide with supposedly parallel vertices",  \
            (int)((_i) + 1));                                                      \
    MESSAGE(string);                                                               \
} while (0)

#define DEBUGREFINEPROCESSPACKETRGB(_i)                                             \
do                                                                                  \
{                                                                                   \
    static char                string[256];                                         \
                                                                                    \
    sprintf(string,                                                                 \
            "RGBAs cluster %d does not coincide with supposedly parallel vertices", \
            (int)((_i) + 1));                                                       \
    MESSAGE(string);                                                                \
} while (0)

#define VALIDATESPACE()                                                         \
do                                                                              \
{                                                                               \
    if (idxs->numAlloced != numTris * 3)                                       \
    {                                                                           \
        MESSAGE("Unable to allocate space in triangles cluster");               \
    }                                                                           \
    if (devVerts->numAlloced != numVerts)                                       \
    {                                                                           \
        MESSAGE("Unable to allocate space in screen-space vertices cluster");   \
    }                                                                           \
    if (camVerts->numAlloced != numVerts)                                       \
    {                                                                           \
        MESSAGE("Unable to allocate space in camera-space vertices cluster");   \
    }                                                                           \
    if (objVerts->numAlloced != numVerts)                                       \
    {                                                                           \
        MESSAGE("Unable to allocate space in object-space vertices cluster");   \
    }                                                                           \
} while (0)

#endif /* (defined(RWDEBUG)) */

#if (!defined(DEBUGREFINEPROCESSPACKETUV))
#define DEBUGREFINEPROCESSPACKETUV(_i) /* No op */
#endif /* (!defined(DEBUGREFINEPROCESSPACKETUV)) */

#if (!defined(DEBUGREFINEPROCESSPACKETRGB))
#define DEBUGREFINEPROCESSPACKETRGB(_i) /* No op */
#endif /* (!defined(DEBUGREFINEPROCESSPACKETRGB)) */

#if (!defined(VALIDATESPACE))
#define VALIDATESPACE()        /* No op */
#endif /* (!defined(VALIDATESPACE)) */

static              RwBool
_refineProcessPacket(RxPipelineNodeInstance * self,
                     RxPacket * packet, RxHeap * heap, RwUInt32 depth,
                     RwUInt32 numExtraUVs, RwUInt32 numExtraRGBAs)
{
    RxMeshStateVector  *meshData;
    RxCluster          *idxs, *meshState, *devVerts;
    RxCluster          *camVerts, *objVerts, *camNorms;
    RxCluster         **extraUVs, **extraRGBAs;
    RwUInt32            i, numVerts, numTris, numPresentUVs,
        numPresentRGBAs;

    refineRefineData    refineData;
    refineCameraData    camData;

    RWFUNCTION(RWSTRING("_refineProcessPacket"));

    extraUVs = (RxCluster **)NULL;
    if (numExtraUVs != 0)
    {
        extraUVs = (RxCluster **)
            RxHeapAlloc(heap, numExtraUVs * sizeof(RxCluster *));
        RWASSERT(NULL != extraUVs);
    }

    extraRGBAs = (RxCluster **)NULL;
    if (numExtraRGBAs != 0)
    {
        extraRGBAs = (RxCluster **)
            RxHeapAlloc(heap, numExtraRGBAs * sizeof(RxCluster *));
        RWASSERT(NULL != extraRGBAs);
    }

    camVerts = RxClusterLockWrite(packet, 0, self);
    devVerts = RxClusterLockWrite(packet, 1, self);
    idxs = RxClusterLockWrite(packet, 2, self);
    meshState = RxClusterLockWrite(packet, 3, self);
    objVerts = RxClusterLockRead(packet, 4);
    camNorms = RxClusterLockWrite(packet, 5, self);

    RWASSERT((devVerts != NULL) && (devVerts->numUsed > 0));
    RWASSERT((camVerts != NULL) && (camVerts->numUsed > 0));
    RWASSERT((idxs != NULL) && (idxs->numUsed > 0));
    RWASSERT((meshState != NULL) && (meshState->numUsed > 0));
    RWASSERT((objVerts != NULL) && (objVerts->numUsed > 0));
    RWASSERT((camNorms != NULL) && (camNorms->numUsed > 0));

    meshData = RxClusterGetCursorData(meshState, RxMeshStateVector);

    numTris = (depth * depth) * meshData->NumElements;
    numVerts = meshData->NumVertices +
        (meshData->NumElements *
         ((((depth + 1) * (depth + 2)) >> 1) - 3));

    /* Grab any extra UV clusters we ned to interpolate */
    numPresentUVs = 0;
    for (i = 0; i < numExtraUVs; i++)
    {
        extraUVs[numPresentUVs] =
            RxClusterLockWrite(packet, NUMCLUSTERSOFINTEREST + i, self);
        RWASSERT(NULL != extraUVs[numPresentUVs]);
        /* Make sure the cluster has data - if not, some node above
         * us decided not to produce a set of UVs this time, which is
         * fine by us - less work to do! */
        if (extraUVs[numPresentUVs]->numUsed > 0)
        {
            /* Check the UVs coincide with the current vertices */
            RWASSERT(extraUVs[numPresentUVs]->numUsed
                     == meshData->NumVertices);
            extraUVs[numPresentUVs] =
                RxClusterResizeData(extraUVs[numPresentUVs], numVerts);
            RWASSERT(NULL != extraUVs[numPresentUVs]);
            numPresentUVs++;
        }
    }

    numPresentRGBAs = 0;
    for (i = 0; i < numExtraRGBAs; i++)
    {
        extraRGBAs[numPresentRGBAs] =
            RxClusterLockWrite(packet,
                               NUMCLUSTERSOFINTEREST + numExtraUVs + i,
                               self);
        RWASSERT(NULL != extraRGBAs[numPresentRGBAs]);
        if (extraRGBAs[numPresentRGBAs]->numUsed > 0)
        {
            /* Check the RGBAs coincide with the current vertices */
            RWASSERT(extraRGBAs[numPresentRGBAs]->numUsed
                     == meshData->NumVertices);
            extraRGBAs[numPresentRGBAs] =
                RxClusterResizeData(extraRGBAs[numPresentRGBAs],
                                    numVerts);
            RWASSERT(NULL != extraRGBAs[numPresentRGBAs]);
            numPresentRGBAs++;
        }
    }

    idxs = RxClusterResizeData(idxs, numTris * 3);
    RWASSERT(NULL != idxs);
    devVerts = RxClusterResizeData(devVerts, numVerts);
    RWASSERT(NULL != devVerts);
    camVerts = RxClusterResizeData(camVerts, numVerts);
    RWASSERT(NULL != camVerts);
    camNorms = RxClusterResizeData(camNorms, numVerts);
    RWASSERT(NULL != camNorms);

    refineData.oldNumTris = meshData->NumElements;
    refineData.newNumTris = numTris;

    refineData.oldNumVerts = meshData->NumVertices;
    refineData.newNumVerts = numVerts;

    refineData.depth = depth;

    refineData.idxs = idxs;
    refineData.objVerts = objVerts;
    refineData.camVerts = camVerts;
    refineData.camNorms = camNorms;
    refineData.devVerts = devVerts;

    refineData.numPresentUVs = numPresentUVs;
    refineData.numPresentRGBAs = numPresentRGBAs;

    refineData.extraUVs = extraUVs;
    refineData.extraRGBAs = extraRGBAs;

    /* Generate the refinement */
    _rtrefineSetupCamera(&camData);
    if (_refineGenerateRefinement(&refineData, &camData, heap) == FALSE)
    {
        MESSAGE("Error in generating refinement");

        if (extraUVs != NULL)
            RxHeapFree(heap, extraUVs);
        if (extraRGBAs != NULL)
            RxHeapFree(heap, extraRGBAs);
        RWRETURN(FALSE);
    }

    /* getting there, finally generate new indices */
    _rtrefineGenerateNewVertIndex(&refineData);

    meshData->NumVertices = numVerts;
    meshData->NumElements = numTris;

    idxs->numUsed = meshData->NumElements * 3;
    devVerts->numUsed = meshData->NumVertices;
    camVerts->numUsed = meshData->NumVertices;
    camNorms->numUsed = meshData->NumVertices;
    for (i = 0; i < numPresentUVs; i++)
    {
        extraUVs[i]->numUsed = meshData->NumVertices;
    }
    for (i = 0; i < numPresentRGBAs; i++)
    {
        extraRGBAs[i]->numUsed = meshData->NumVertices;
    }

    /* objverts now invalid. kill the cluster */
    RxClusterDestroyData(objVerts);

    if (extraUVs != NULL)
        RxHeapFree(heap, extraUVs);
    if (extraRGBAs != NULL)
        RxHeapFree(heap, extraRGBAs);

    RWRETURN(TRUE);
}

/*****************************************************************************
 _RefineNodePipelineNodeInitFn

 Initialises the private data (refinement ON by default)
*/

static              RwBool
_RefineNodePipelineNodeInitFn(RxPipelineNode * self)
{
    RWFUNCTION(RWSTRING("_RefineNodePipelineNodeInitFn"));

    if (self)
    {
        RpNodeRefineData    data;

        data.refineOn = TRUE;
        data.numExtraUVs = 0;
        data.numExtraRGBAs = 0;

        *((RpNodeRefineData *) self->privateData) = data;
        RWRETURN(TRUE);
    }
    RWRETURN(FALSE);
}

/*****************************************************************************
 _refineNode
*/

static              RwBool
_refineNode(RxPipelineNodeInstance * self,
            const RxPipelineNodeParam * params)
{
    RwInt32             output;
    RpNodeRefineData   *refineData;
    RxPacket           *packet;
    RxHeap             *heap;
    RwInt32             offset;
    RxCluster          *state;
    RpAtomic           *atom;
    RxMeshStateVector  *stateData;
    RpRefineAtomicExtension *extension;
    RwObject           *sourceObject;
    RwInt32             depth, numExtraUVs, numExtraRGBAs;

    RWFUNCTION(RWSTRING("_refineNode"));

    RWASSERT(NULL != self);

    packet = (RxPacket *)RxPacketFetch(self);
    RWASSERT((RxPacket *)NULL != packet);

    RWASSERT(NULL != params);
    heap = RxPipelineNodeParamGetHeap(params);
    RWASSERT(NULL != heap);

    refineData = (RpNodeRefineData *) self->privateData;
    RWASSERT(NULL != refineData);

    /* Cheap early out if this node's toggled off */
    if (refineData->refineOn == FALSE)
    {
        RxPacketDispatch(packet, REFINEPASSTHROUGH, self);
        RWRETURN(TRUE);
    }

    rpRefineGlobals.sseFlag = 0;

    state = RxClusterLockRead(packet, 3);
    stateData = RxClusterGetCursorData(state, RxMeshStateVector);

    /* Default output is 0, pass-through. */
    output = REFINEPASSTHROUGH;

    /* We can only handle tri list at present. */
    RWASSERT(stateData->PrimType == rwPRIMTYPETRILIST);

    /* Quick hack. Typecase the stateData and check the flag to see if it an
     * RpAtomic before refining.
     */
    sourceObject = (RwObject *) stateData->DataObject;
    RWASSERT(NULL != sourceObject);
    RWASSERT(sourceObject->type == (RwUInt8) rpATOMIC);

    atom = (RpAtomic *) sourceObject;

    offset = rpRefineGlobals.atmExtOffset;
    extension = RPREFINEOFFSET(atom, offset);

    if (extension != NULL)
    {
        depth = (*extension->selectDepth) (atom);

        numExtraUVs = refineData->numExtraUVs;
        numExtraRGBAs = refineData->numExtraRGBAs;

        if (depth > 1)
        {
            /* do the business */
            if (_refineProcessPacket
                (self, packet, heap, depth, numExtraUVs,
                 numExtraRGBAs) == FALSE)
            {
                RxPacketDestroy(packet, self);
                RWRETURN(FALSE);
            }

            /* We have refinement! So output to channel 1. */
            output = REFINEOUT;
        }
    }

    /* Output the packet to the appropriate output */
    RxPacketDispatch(packet, output, self);

    RWRETURN(TRUE);
}

/**
 * \ingroup rprefine
 * \ref RxNodeDefinitionGetRefineCSL
 * returns a pointer to a node implementing refinement in custom pipelines
 *
 * This node refines triangles in the triangles cluster according
 * to the specified criteria
 *
 * \verbatim
   The node has two outputs.
   The first is sent data which has been refined in camera space, when
   refinemnet is active.
   The second is sentunmodified data, when refinemnet is inactive.
  
   The input requirements of this node:
  
   RxClCamSpace3DVertices - required
   RxClScrSpace2DVertices - required
   RxClIndices - required
   RxClMeshState - required
   RxClObjSpace3DVertices - required
   RxClCamNorms - required
  
   The characteristics of the first of this node's outputs, 
  
   RxClCamSpace3DVertices - valid
   RxClScrSpace2DVertices - valid
   RxClIndices - valid
   RxClMeshState - valid
   RxClObjSpace3DVertices - invalid
   RxClCamNorms - valid
  
   The characteristics of the second of this node's outputs:
  
   RxClCamSpace3DVertices - no change
   RxClScrSpace2DVertices - no change
   RxClIndices - no change
   RxClMeshState - no change
   RxClObjSpace3DVertices - no change
   RxClCamNorms - no change
   \endverbatim
 *
 * \return pointer to node for refinemnetin custom pipelines on success,
 * NULL otherwise
 */
RxNodeDefinition   *
RxNodeDefinitionGetRefineCSL(void)
{
    static RxClusterRef gNodeClusters[NUMCLUSTERSOFINTEREST] = /* */
    {
        {&RxClCamSpace3DVertices, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClScrSpace2DVertices, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClIndices, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClMeshState, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClObjSpace3DVertices, rxCLALLOWABSENT, rxCLRESERVED},
        {&RxClCamNorms, rxCLALLOWABSENT, rxCLRESERVED}
    };

    static RxClusterValidityReq gNodeReqs[NUMCLUSTERSOFINTEREST] = /* */
        /* parallel to ClusterRefs */
    {
        rxCLREQ_REQUIRED,
        rxCLREQ_REQUIRED,
        rxCLREQ_REQUIRED,
        rxCLREQ_REQUIRED,
        rxCLREQ_REQUIRED,
        rxCLREQ_REQUIRED
    };

    static RxClusterValid gNodeRefineOut[NUMCLUSTERSOFINTEREST] = /* */
        /* parallel to ClusterRefs */
    {
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_INVALID,      /* ObjVerts invalidated! Not enough of 'em any more */
        rxCLVALID_VALID
    };

    static RxClusterValid gNodePassThrough[NUMCLUSTERSOFINTEREST] = /* */
        /* parallel to ClusterRefs */
    {
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID,
        rxCLVALID_VALID
    };

    static RwChar       _PassThrough[] = RWSTRING("PassThrough");
    static RwChar       _RefineOut[] = RWSTRING("RefineOut");

    static RxOutputSpec gNodeOuts[NUMOUTPUTS] = /* */
    {
        {
         _RefineOut,           /* Name */
         gNodeRefineOut,       /* OutputClusters */
         rxCLVALID_NOCHANGE},
        {
         _PassThrough,         /* Name */
         gNodePassThrough,     /* OutputClusters */
         rxCLVALID_NOCHANGE}

    };                         /* AllOtherClusters */

    static RwChar       _Refine_csl[] = RWSTRING("Refine.csl");

    static RxNodeDefinition nodeRefineCSL = /* */
    {
        _Refine_csl,            /* Name */
        {_refineNode,          /* +-- nodebody */
         (RxNodeInitFn)NULL,
         (RxNodeTermFn)NULL,
         _RefineNodePipelineNodeInitFn, /* +-- pipelinenodeinit */
         (RxPipelineNodeTermFn)NULL,
         (RxPipelineNodeConfigFn)NULL,
         (RxConfigMsgHandlerFn)NULL },
        {                      /* Io */
         NUMCLUSTERSOFINTEREST, /* +-- NumClustersOfInterest */
         gNodeClusters,        /* +-- ClustersOfInterest */
         gNodeReqs,            /* +-- InputRequirements */
         NUMOUTPUTS,           /* +-- NumOutputs */
         gNodeOuts             /* +-- Outputs */
         }
        , sizeof(RpNodeRefineData), /* no private data needed for pipeline nodes made from this */
        (RxNodeDefEditable) FALSE, /* node definition not editable (it's a global) */
        0
    };                         /* how many pipeline nodes have been made from this definition? */

    RxNodeDefinition   *result = &nodeRefineCSL;

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetRefineCSL"));
/*
    RWMESSAGE((RWSTRING("result %p"), result));
 */

    rpRefineGlobals.sseFlag = 0;

    RWRETURN(result);
}
