/* *INDENT-OFF* */

/*
 * nodeps2matinstance
 * A PS2-specific node to instance meshes 
 * 
 * Copyright (c) Criterion Software Limited
 */
/****************************************************************************
 *                                                                          *
 * module : nodeps2matinstance.c                                            *
 *                                                                          *
 * purpose: yawn...                                                         *
 *                                                                          *
 ****************************************************************************/

/****************************************************************************
 includes
 */

#include "rwcore.h"
#include "skyisms.h"
#include "nodeps2matinstance.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = 
    "@@@@(#)$Id: nodeps2matinstance.c,v 1.193 2001/09/12 14:15:38 iestynb Exp $";
#endif /* (!defined(DOXYGEN)) */


/****************************************************************************
 local defines
 */

static RxClusterRef nodeClusters[] =
{
    { &RxClPS2DMASessionRecord, rxCLALLOWABSENT, rxCLRESERVED },
    { &RxClPS2Mesh, rxCLALLOWABSENT, rxCLRESERVED }
};

#define NUMCLUSTERSOFINTEREST \
    ((sizeof(nodeClusters))/(sizeof(nodeClusters[0])))

#define PRIVATEDATATYPE rwPS2MatInstancePvtData

#define MESSAGE(_string) \
    RwDebugSendMessage(rwDEBUGMESSAGE, "PS2MatInstance.csl", (_string))

/* VU related defines. These really come from elsewhere */
#define VU1_MAX_TS_INPUT_SIZE 256
#define VU1_MAX_TL_INPUT_SIZE 64

/* Seems a reasonable value given TS size is in vertices not triangles */
#define VU1_MAX_PL_INPUT_SIZE VU1_MAX_TS_INPUT_SIZE

#define OVERRIDELIGHTx
#define SUBSISTLIGHTx

/* Use this to get compatibility ITOP */
#define OLDITOP

/* Do waht it takes to make things work for now */
#define HACKSx

/* Define to get silly amounts of info on stdout */
#define REDEBUGx
#define DMADUMPx
#define DMADUMPDATAx

#define CLEARMEMx

#ifdef CLEARMEM
unsigned long clearMemVal=1;
#endif /* CLEARMEMVAL */

#ifndef DEBUGMARK
#define DEBUGMARK() (0l)
#endif /* DEBUGMARK */

/* Get CL_CODE strings for error messages */
#define GETCLCODESTRING(_cl_code, _string)  \
MACRO_START                                 \
{                                           \
    switch(_cl_code)                        \
    {                                       \
    case CL_XYZ:                            \
        strcpy(_string, "XYZ");             \
        break;                              \
    case CL_UV:                             \
        strcpy(_string, "UV");              \
        break;                              \
    case CL_UV2:                            \
        strcpy(_string, "UV2");             \
        break;                              \
    case CL_RGBA:                           \
        strcpy(_string, "RGBA");            \
        break;                              \
    case CL_NORMAL:                         \
        strcpy(_string, "Normal");          \
        break;                              \
    case CL_USER1:                          \
        strcpy(_string, "User1");           \
        break;                              \
    case CL_USER2:                          \
        strcpy(_string, "User2");           \
        break;                              \
    case CL_USER3:                          \
        strcpy(_string, "User3");           \
        break;                              \
    case CL_USER4:                          \
        strcpy(_string, "User4");           \
        break;                              \
    default:                                \
        strcpy(_string, "!UNKNOWN!");       \
        break;                              \
    }                                       \
}                                           \
MACRO_STOP


/* Instancing support defines:
 *
 * Allows one piece of code to instance from any of the
 * six primitive types, indexed or unindexed. Uses a struct
 * of small values (e.g bytes as 4x2-bit LUTs) and redundant
 * cheap calcs to remove predication or code duplication */

#define INDEXDECLARE()                                                          \
        RwUInt32       cntGen, indOffset = 0, startIndOffset, modCnt = 0;       \
        RwUInt32       vertRatio, vertLeadup;                                   \
        const RwUInt8  *shiftMask, *shiftMask2, *vertInc, *swap;                \
        const RxVertexIndex *indexArray
/* N.B Trailing semicolon deliberately omitted from INDEXDECLARE */

#define INDEXSETUP(_flags, _indices)                                          \
MACRO_START                                                                   \
{                                                                             \
    static const rwPS2indexLUTs IndexLUT =                                    \
    {                                                                         \
        {0},                                         /* Fake index 'array' */ \
        {0, 0, {~0},         { 0},       {1}},       /* Normal (default) */   \
        {9, 1, {~0, ~0, ~0}, {~0, 0, 0}, {0, 1, 0}}, /* TriFan */             \
        {1, 0, {~0, ~0},     { 0, 0},    {1, 0}}     /* PolyLine */           \
    };                                                                        \
                                                                              \
    if (_flags & rpMESHHEADERTRIFAN)                                          \
    {                                                                         \
        cntGen         = IndexLUT.triFan.counterGen;                          \
        startIndOffset = IndexLUT.triFan.startIndOffset;                      \
        shiftMask      = &(IndexLUT.triFan.shiftMask[0]);                     \
        shiftMask2     = &(IndexLUT.triFan.shiftMask2[0]);                    \
        vertInc        = &(IndexLUT.triFan.vertInc[0]);                       \
        vertRatio      = 3;                                                   \
        vertLeadup     = 2;                                                   \
    }                                                                         \
    else if (_flags & rpMESHHEADERPOLYLINE)                                   \
    {                                                                         \
        cntGen         = IndexLUT.polyLine.counterGen;                        \
        startIndOffset = IndexLUT.polyLine.startIndOffset;                    \
        shiftMask      = &(IndexLUT.polyLine.shiftMask[0]);                   \
        shiftMask2     = &(IndexLUT.polyLine.shiftMask2[0]);                  \
        vertInc        = &(IndexLUT.polyLine.vertInc[0]);                     \
        vertRatio      = 2;                                                   \
        vertLeadup     = 1;                                                   \
    }                                                                         \
    else                                                                      \
    {                                                                         \
        cntGen         = IndexLUT.normal.counterGen;                          \
        startIndOffset = IndexLUT.normal.startIndOffset;                      \
        shiftMask      = &(IndexLUT.normal.shiftMask[0]);                     \
        shiftMask2     = &(IndexLUT.normal.shiftMask2[0]);                    \
        vertInc        = &(IndexLUT.normal.vertInc[0]);                       \
        vertRatio      = 1;                                                   \
        vertLeadup     = 0;                                                   \
    }                                                                         \
    RWASSERT(sizeof(RwImVertexIndex) == sizeof(RxVertexIndex));               \
    if (! ((_flags) & rpMESHHEADERUNINDEXED) )                                \
    {                                                                         \
        /* The roles of mask and mask2 swap */                                \
        /* between INdexed and UNindexed */                                   \
        swap        = shiftMask;                                               \
        shiftMask  = shiftMask2;                                              \
        shiftMask2 = swap;                                                     \
        RWASSERT(_indices != NULL);                                           \
        indexArray = (_indices);                                              \
    }                                                                         \
    else                                                                      \
    {                                                                         \
        indexArray = &(IndexLUT.fakeIndices[0]);                              \
    }                                                                         \
}                                                                             \
MACRO_STOP                                                                    \

/* To be called before each instancing loop */
#define INDEXRESET() (modCnt = 0, indOffset = startIndOffset)

/* Gets the current index */
#define INDEXGET()                                                          \
    ( (indOffset << shiftMask2[modCnt]) + indexArray[indOffset << shiftMask[modCnt]] )

/* Increment after each vertex */
#define INDEXINC()                          \
    ( indOffset += vertInc[modCnt],         \
      modCnt = 3&(cntGen >> (modCnt << 1))) \

/* Reversal only affects tristrips and their counter is
 * constant at zero, so it's simple */
#define STRIPREVERSE(_reverse)   ( indOffset -= (_reverse) )


/****************************************************************************
 local (static) globals
 */

/* Used in the above instancing support macros */



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

   Functions

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

/****************************************************************************
 reDestroyCallback

 placeholder
 */
void
reDestroyCallback(RwResEntry *resEntry)
{
    volatile RwUInt32 *count = (volatile RwUInt32 *)
        &(((rwPS2ResEntryHeader*)(resEntry+1))->refCnt);

    RWFUNCTION(RWSTRING("reDestroyCallback"));

    while (*count != 0)
    {
        _sweFlush();
    }

    RWRETURNVOID();
}

/****************************************************************************
 RabinsConstructionTimeCode
 */
static RwBool /* success? */
RabinsConstructionTimeCode(rwPS2MatInstancePvtData *pvtdata)
{
    RwBool result = FALSE;
    RwBool totallyOpaque = TRUE;
    RwBool pointList;
    int sizeOnVU;
    int opaqueStep;
    int numStripes;
    int offsetOnVU;
    int dataTmp;
    int prevSize;
    int prevElementSize;
    int i;

    RWFUNCTION(RWSTRING("RabinsConstructionTimeCode"));

    /* We have to do the calculations twice as don't know until */
    /* pipeline execute time if we will be instancing a list or strip */

    /* Futher, we don't know how may verts there will be in the meshes */
    /* that pass down this pipe. All we can do is calculate block sizes */
    /* and strides */

    /* This is really inefficient, but I want it to work before I try */
    /* and make it neat */


    /* To support point lists, we reuse the triList structure which is
     * very similar. We don't bother setting up the triStrip data cos
     * a pointList pipeline can *only* handle pointLists, not triLists
     * or triStrips. */
    pointList = (pvtdata->pipeType&rpMESHHEADERPOINTLIST)?TRUE:FALSE;

#if (!defined(HACKS))

    /* Attributes are already set up by the pipelinenode init function */

#else /* !HACKS */
    /* While we are testing, none of the cluster stuff works */
    DPUT_VIF1_ERR(DGET_VIF1_ERR() | 2);
    {
        pvtdata->breakoutsbitfield =
            /* (1U<<CL_XYZ)    |
             * (1U<<CL_UV)     |
             * (1U<<CL_UV2)    |
             * (1U<<CL_RGBA)   |
             * (1U<<CL_NORMAL) |
             * (1U<<CL_USER1)  |
             * (1U<<CL_USER2)  |
             * (1U<<CL_USER3)  |
             * (1U<<CL_USER4)  | */
            0;

        for (i=0; i<((int)(CL_MAXCL)); i++)
        {
            switch (i)
            {
                case CL_XYZ:
                    pvtdata->clinfo[CL_XYZ].attrib =
                        CL_ATTRIB_REQUIRED |
                        /* CL_ATTRIB_READ |
                         * CL_ATTRIB_WRITE |
                         * CL_ATTRIB_DONT_FILL |
                         * CL_ATTRIB_PLACEHOLDER |
                         * CL_ATTRIB_OPAQUE |
                         * CL_ATTRIB_STATIC |
                         * CL_S32 |
                         * CL_V2_32 |
                         * CL_V2_16 | */
                        CL_V3_32 |
                        /* CL_V4_32 |
                         * CL_V4_16 |
                         * CL_V4_8  |
                         * CL_USN   | */
                        0;

                    /* pvtdata->clinfo[i].attrib = 0; */
                    break;
                case CL_UV:
                    pvtdata->clinfo[CL_UV].attrib =
                        CL_ATTRIB_REQUIRED |
                        /* CL_ATTRIB_READ |
                         * CL_ATTRIB_WRITE |
                         * CL_ATTRIB_DONT_FILL |
                         * CL_ATTRIB_PLACEHOLDER |
                         * CL_ATTRIB_OPAQUE |
                         * CL_ATTRIB_STATIC |
                         * CL_S32 | */
                        CL_V2_32 |
                        /* CL_V2_16 |
                         * CL_V3_32 |
                         * CL_V4_32 |
                         * CL_V4_16 |
                         * CL_V4_8  |
                         * CL_USN   | */
                        0;

                    /* pvtdata->clinfo[i].attrib = 0; */
                    break;
                case CL_UV2:
                    pvtdata->clinfo[CL_UV2].attrib =
                        CL_ATTRIB_REQUIRED |
                        /* CL_ATTRIB_READ |
                         * CL_ATTRIB_WRITE |
                         * CL_ATTRIB_DONT_FILL |
                         * CL_ATTRIB_PLACEHOLDER |
                         * CL_ATTRIB_OPAQUE |
                         * CL_ATTRIB_STATIC |
                         * CL_S32 |
                         * CL_V2_32 |
                         * CL_V2_16 |
                         * CL_V3_32 | */
                        CL_V4_32 |
                        /* CL_V4_16 |
                         * CL_V4_8  |
                         * CL_USN   | */
                        0;

                    /* pvtdata->clinfo[i].attrib = 0; */
                    break;
                case CL_RGBA:
                    pvtdata->clinfo[CL_RGBA].attrib =
                        CL_ATTRIB_REQUIRED |
                        /* CL_ATTRIB_READ |
                         * CL_ATTRIB_WRITE |
                         * CL_ATTRIB_DONT_FILL |
                         * CL_ATTRIB_PLACEHOLDER |
                         * CL_ATTRIB_OPAQUE |
                         * CL_ATTRIB_STATIC |
                         * CL_S32 |
                         * CL_V2_32 |
                         * CL_V2_16 |
                         * CL_V3_32 |
                         * CL_V4_32 |
                         * CL_V4_16 | */
                        CL_V4_8  |
                        CL_USN   |
                        0;

                    /* pvtdata->clinfo[i].attrib = 0; */
                    break;
                case CL_NORMAL:
                    /* This is a bit special as in opaque it is V3_8 */
                    pvtdata->clinfo[CL_NORMAL].attrib =
                        CL_ATTRIB_REQUIRED |
                        /* CL_ATTRIB_READ |
                         * CL_ATTRIB_WRITE |
                         * CL_ATTRIB_DONT_FILL |
                         * CL_ATTRIB_PLACEHOLDER |
                         * CL_ATTRIB_OPAQUE |
                         * CL_ATTRIB_STATIC |
                         * CL_S32 |
                         * CL_V2_32 |
                         * CL_V2_16 |
                         * CL_V3_32 |
                         * CL_V4_32 |
                         * CL_V4_16 | */
                        CL_V4_8  |
                        /* CL_USN   | */
                        0;

                    /* pvtdata->clinfo[i].attrib = 0; */
                    break;
                case CL_USER1:
                    pvtdata->clinfo[CL_USER1].attrib =
                        /* CL_ATTRIB_REQUIRED |
                         * CL_ATTRIB_READ |
                         * CL_ATTRIB_WRITE |
                         * CL_ATTRIB_DONT_FILL |
                         * CL_ATTRIB_PLACEHOLDER |
                         * CL_ATTRIB_OPAQUE |
                         * CL_ATTRIB_STATIC |
                         * CL_S32 |
                         * CL_V2_32 |
                         * CL_V2_16 |
                         * CL_V3_32 |
                         * CL_V4_32 |
                         * CL_V4_16 |
                         * CL_V4_8  |
                         * CL_USN   | */
                        0;

                    pvtdata->clinfo[i].attrib = 0;
                    break;
                case CL_USER2:
                    pvtdata->clinfo[CL_USER2].attrib =
                        /* CL_ATTRIB_REQUIRED |
                         * CL_ATTRIB_READ |
                         * CL_ATTRIB_WRITE |
                         * CL_ATTRIB_DONT_FILL |
                         * CL_ATTRIB_PLACEHOLDER |
                         * CL_ATTRIB_OPAQUE |
                         * CL_ATTRIB_STATIC |
                         * CL_S32 |
                         * CL_V2_32 |
                         * CL_V2_16 |
                         * CL_V3_32 |
                         * CL_V4_32 |
                         * CL_V4_16 |
                         * CL_V4_8  |
                         * CL_USN   | */
                        0;

                    pvtdata->clinfo[i].attrib = 0;
                    break;
                case CL_USER3:
                    pvtdata->clinfo[CL_USER3].attrib =
                        /* CL_ATTRIB_REQUIRED |
                         * CL_ATTRIB_READ |
                         * CL_ATTRIB_WRITE |
                         * CL_ATTRIB_DONT_FILL |
                         * CL_ATTRIB_PLACEHOLDER |
                         * CL_ATTRIB_OPAQUE |
                         * CL_ATTRIB_STATIC |
                         * CL_S32 |
                         * CL_V2_32 |
                         * CL_V2_16 |
                         * CL_V3_32 |
                         * CL_V4_32 |
                         * CL_V4_16 |
                         * CL_V4_8  |
                         * CL_USN   | */
                        0;

                    pvtdata->clinfo[i].attrib = 0;
                    break;
                case CL_USER4:
                    pvtdata->clinfo[CL_USER4].attrib =
                        /* CL_ATTRIB_REQUIRED |
                         * CL_ATTRIB_READ |
                         * CL_ATTRIB_WRITE |
                         * CL_ATTRIB_DONT_FILL |
                         * CL_ATTRIB_PLACEHOLDER |
                         * CL_ATTRIB_OPAQUE |
                         * CL_ATTRIB_STATIC |
                         * CL_S32 |
                         * CL_V2_32 |
                         * CL_V2_16 |
                         * CL_V3_32 |
                         * CL_V4_32 |
                         * CL_V4_16 |
                         * CL_V4_8  |
                         * CL_USN   | */
                        0;

                    pvtdata->clinfo[i].attrib = 0;
                    break;
            }
        }
    }
#endif /* HACKS */

#ifdef FASTMORPH
    pvtdata->clinfo[CL_MAXCL].attrib = CL_ATTRIB_REQUIRED
                                       | CL_ATTRIB_OPAQUE
                                       | CL_V3_32;

    pvtdata->clinfo[CL_MAXCL+1].attrib = CL_ATTRIB_REQUIRED
                                         | CL_ATTRIB_OPAQUE
                                         | CL_V4_8;
#endif /* FASTMORPH */
    /* Are we totally opaque? How big on the VU */
    totallyOpaque = TRUE;
    sizeOnVU = 0;
    for (i=0; i<((int)(CL_MAXCL)); i++)
    {
        if ((pvtdata->clinfo[i].attrib & CL_ATTRIB_REQUIRED)
            || (pvtdata->clinfo[i].attrib & CL_ATTRIB_PLACEHOLDER))
        {
            if ((pvtdata->clinfo[i].attrib & CL_ATTRIB_REQUIRED)
                && (!(pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE)))
            {
                totallyOpaque = FALSE;
            }
            sizeOnVU += 1; /* pvtdata->clinfo[i].size; */
        }
    }
    /* Save for future reference */
    pvtdata->totallyOpaque = (RwUInt8)totallyOpaque;
    pvtdata->sizeOnVU = (RwUInt8)sizeOnVU;

    /* If the VU input "vertex" is zero qw we die */
    /* Eventually we should support this (or zero vertices) */
    RWASSERT(sizeOnVU);

    /* If the calculated size on the VU isn't the same as the user set size */
    RWASSERT(sizeOnVU == pvtdata->checkSizeOnVU);

    if ((!sizeOnVU) || (sizeOnVU != pvtdata->checkSizeOnVU))
    {
        RWRETURN(FALSE);
    }

    /* Figure out the batch size and batches per tag for tristrips */
    if (FALSE == pointList)
    {
        if (totallyOpaque)
        {
            /* More optimal upload when we don't need to cope with striped data
             * as well (the duplication of the 2 verts per batch is done by
             * copying them in-place in opaque arrays - you can't do that for
             * broken-out arrays and if there are any broken-out arrays, you
             * can't have multiple batches hanging off one tag whereas you
             * can if there are none. #*!N.B!*# the mechanism used in the
             * instancing code to deal with this is not obvious. It relies on
             * fieldRec[#].numVerts being set up differently when things are
             * opaque/not - for opaque clusters it's set to batchSize, for
             * non-opaque ones, it's set (later, in RabinsMatInstanceCode) to
             * the number of verts in the whole mesh).
             * When we are totally opaque, we use fewer DMA tags (no DMA ref
             * tags inserted between batches, pointing at broken-out data),
             * so skip size is smaller and we can use tags that say "upload
             * N blocks of this size" */
            pvtdata->triStrip.batchSize = (pvtdata->vu1MaxTSInputSize/sizeOnVU)&~3;

            /* This includes the rather interesting assumption that totally */
            /* opaque means all fields required */
            pvtdata->triStrip.batchesPerTag = 65535/(5
                                                     + ((3*pvtdata->triStrip.batchSize+3)>>2)
                                                     + ((4*pvtdata->triStrip.batchSize+3)>>2)
                                                     + ((pvtdata->triStrip.batchSize+3)>>2)
                                                     + ((pvtdata->triStrip.batchSize*3+15)>>4));
#ifdef FASTMORPH
            pvtdata->triStrip.morphBatchSize = (pvtdata->vu1MaxTSInputSize
                                                /(sizeOnVU+2))&~3;
            pvtdata->triStrip.morphBatchesPerTag = 65535
                           /(7
                             + ((3*pvtdata->triStrip.morphBatchSize+3)>>2)
                             + ((4*pvtdata->triStrip.morphBatchSize+3)>>2)
                             + ((pvtdata->triStrip.morphBatchSize+3)>>2)
                             + ((pvtdata->triStrip.morphBatchSize*3+15)>>4)
                             /* Extra set of xyz and norm */
                             + ((3*pvtdata->triStrip.morphBatchSize+3)>>2)
                             + ((pvtdata->triStrip.morphBatchSize*3+15)>>4));
#endif /* FASTMORPH */
        }
        else
        {
            /* We need 2 verts over a qw size so we can restart on a */
            /* qw boundary */
            pvtdata->triStrip.batchSize = (((pvtdata->vu1MaxTSInputSize/sizeOnVU)-2)&~3)+2;
            pvtdata->triStrip.batchesPerTag = 1;
#ifdef FASTMORPH
            pvtdata->triStrip.morphBatchSize = (((pvtdata->vu1MaxTSInputSize
                                                  /(sizeOnVU+2))-2)&~3)+2;
            pvtdata->triStrip.morphBatchesPerTag = 1;
#endif /* FASTMORPH */
        }
    }

    /* Figure out the batch size and batches per tag for trilists */
    if (FALSE == pointList)
    {
        pvtdata->triList.batchSize = ((pvtdata->vu1MaxTLInputSize/sizeOnVU)/12)*12;
#ifdef FASTMORPH
        pvtdata->triList.morphBatchSize = ((pvtdata->vu1MaxTLInputSize
                                            /(sizeOnVU+2))/12)*12;
#endif /* FASTMORPH */
    }
    else
    {

        pvtdata->triList.batchSize = (pvtdata->vu1MaxPLInputSize/sizeOnVU)&~3;
#ifdef FASTMORPH
        /* Not convinced this is necessary */
        pvtdata->triList.morphBatchSize = (pvtdata->vu1MaxPLInputSize
                                           /(sizeOnVU+2))&~3;
#endif /* FASTMORPH */
    }
    if (totallyOpaque)
    {
        /* This includes the rather interesting assumption that totally */
        /* opaque means all fields required */
        pvtdata->triList.batchesPerTag = 65535/(5
                                                + ((3*pvtdata->triList.batchSize+3)>>2)
                                                + ((4*pvtdata->triList.batchSize+3)>>2)
                                                + ((pvtdata->triList.batchSize+3)>>2)
                                                + ((pvtdata->triList.batchSize*3+15)>>4));
#ifdef FASTMORPH
        pvtdata->triList.morphBatchesPerTag = 65535
                          /(7
                            + ((3*pvtdata->triList.morphBatchSize+3)>>2)
                            + ((4*pvtdata->triList.morphBatchSize+3)>>2)
                            + ((pvtdata->triList.morphBatchSize+3)>>2)
                            + ((pvtdata->triList.morphBatchSize*3+15)>>4)
                            /* Allow for an extra set of xyz and normals */
                            + ((3*pvtdata->triList.morphBatchSize+3)>>2)
                            + ((pvtdata->triList.morphBatchSize*3+15)>>4));
#endif /* FASTMORPH */
    }
    else
    {
        pvtdata->triList.batchesPerTag = 1;
#ifdef FASTMORPH
        pvtdata->triList.morphBatchesPerTag = 1;
#endif /* FASTMORPH */
    }

    /* We now figure out what the opaque step is for tristrips */
    if (!pointList)
    {
        if (totallyOpaque)
        {
            opaqueStep = 1;
        }
        else
        {
            opaqueStep = 2;
        }
        numStripes = 0;
        for (i=0; i<((int)(CL_MAXCL)); i++)
        {
            if (pvtdata->clinfo[i].attrib  & CL_ATTRIB_REQUIRED)
            {
                if (pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE)
                {
                    RWASSERT((i == CL_XYZ) || (i == CL_UV) || (i == CL_UV2)
                             || (i == CL_RGBA) || (i == CL_NORMAL));
                    /* Per batch size is complicated by the fact the */
                    /* each batch is not a whole multiple of qw */
                    switch (i)
                    {
                        case CL_XYZ:
                            opaqueStep += (3*pvtdata->triStrip.batchSize+3)>>2;
                            break;
                        case CL_UV:
                            opaqueStep += (2*pvtdata->triStrip.batchSize+3)>>2;
                            break;
                        case CL_UV2:
                            opaqueStep += (4*pvtdata->triStrip.batchSize+3)>>2;
                            break;
                        case CL_RGBA:
                            opaqueStep += (pvtdata->triStrip.batchSize+3)>>2;
                            break;
                        case CL_NORMAL:
                            opaqueStep += (3*pvtdata->triStrip.batchSize+15)>>4;
                            break;
                    }
                    /* + vif tag */
                    opaqueStep +=1;
                }
                else
                {
                    /* Dma ref and Dma vnt 0 */
                    opaqueStep += 2;
                    numStripes += 1;
                }
            }
        }
        /* Save for future reference */
        pvtdata->numStripes = numStripes;

        /* Opaque data starts 2*numStripes+1 qw from data */
        /* each following opaque cluster starts 1+prev size */
        dataTmp = 0 + 2*numStripes+1;
        prevSize = 0;
        prevElementSize = 0;
        offsetOnVU = 0;
        for (i=0; i<((int)(CL_MAXCL)); i++)
        {
            if (pvtdata->clinfo[i].attrib  & CL_ATTRIB_REQUIRED)
            {
                pvtdata->triStrip.fieldRec[i].vuoffset = offsetOnVU;
                if (pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE)
                {
                    int batchSize
                        = pvtdata->triStrip.fieldRec[i].numVerts
                        = pvtdata->triStrip.batchSize;
                    dataTmp += prevSize + 1;
                    pvtdata->triStrip.fieldRec[i].dataoffset = dataTmp;
                    pvtdata->triStrip.fieldRec[i].skip = opaqueStep;
                    pvtdata->triStrip.fieldRec[i].reverse = 0;
                    offsetOnVU += 1; /* size on vu */
                    switch (i)
                    {
                        case CL_XYZ:
                            prevSize = (3*batchSize+3)>>2;
                            prevElementSize = 12;
                            break;
                        case CL_UV:
                            prevSize = (2*batchSize+3)>>2;
                            prevElementSize = 8;
                            break;
                        case CL_UV2:
                            prevSize = (4*batchSize+3)>>2;
                            prevElementSize = 16;
                            break;
                        case CL_RGBA:
                            prevSize = (batchSize+3)>>2;
                            prevElementSize = 4;
                            break;
                        case CL_NORMAL:
                            prevSize = (3*batchSize+15)>>4;
                            prevElementSize = 3;
                            break;
                    }
                }
                else
                {
                    pvtdata->triStrip.fieldRec[i].numVerts = 0;
                    pvtdata->triStrip.fieldRec[i].dataoffset = 0;
                    pvtdata->triStrip.fieldRec[i].skip = 0;
                    pvtdata->triStrip.fieldRec[i].reverse = 0;
                    offsetOnVU += 1; /* size on vu */
                }
            }
            else if (pvtdata->clinfo[i].attrib  & CL_ATTRIB_PLACEHOLDER)
            {
                pvtdata->triStrip.fieldRec[i].vuoffset = offsetOnVU;
                pvtdata->triStrip.fieldRec[i].numVerts = 0;
                pvtdata->triStrip.fieldRec[i].dataoffset = 0;
                pvtdata->triStrip.fieldRec[i].skip = 0;
                pvtdata->triStrip.fieldRec[i].reverse = 0;
                offsetOnVU += 1; /* size on vu */
            }
        }
#ifdef FASTMORPH
        if (totallyOpaque)
        {
            opaqueStep = 1;
        }
        else
        {
            opaqueStep = 2;
        }
        numStripes = 0;
        for (i=0; i<((int)(CL_MAXCL + 2)); i++)
        {
            if (pvtdata->clinfo[i].attrib  & CL_ATTRIB_REQUIRED)
            {
                if (pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE)
                {
                    /* Per batch size is complicated by the fact the */
                    /* each batch is not a whole multiple of qw */
                    switch (i)
                    {
                        case CL_XYZ:
                            opaqueStep += (3*pvtdata->triStrip.morphBatchSize+3)>>2;
                            break;
                        case CL_UV:
                            opaqueStep += (2*pvtdata->triStrip.morphBatchSize+3)>>2;
                            break;
                        case CL_UV2:
                            opaqueStep += (4*pvtdata->triStrip.morphBatchSize+3)>>2;
                            break;
                        case CL_RGBA:
                            opaqueStep += (pvtdata->triStrip.morphBatchSize+3)>>2;
                            break;
                        case CL_NORMAL:
                            opaqueStep += (3*pvtdata->triStrip.morphBatchSize+15)>>4;
                            break;
                        case CL_MAXCL:
                            opaqueStep += (3*pvtdata->triStrip.morphBatchSize+3)>>2;
                            break;
                        case CL_MAXCL+1:
                            opaqueStep += (3*pvtdata->triStrip.morphBatchSize+15)>>4;
                            break;
                    }
                    /* + vif tag */
                    opaqueStep +=1;
                }
                else
                {
                    /* Dma ref and Dma vnt 0 */
                    opaqueStep += 2;
                    numStripes += 1;
                }
            }
        }
        /* Save for future reference */
        pvtdata->numStripes = numStripes;

        /* Opaque data starts 2*numStripes+1 qw from data */
        /* each following opaque cluster starts 1+prev size */
        dataTmp = 0 + 2*numStripes+1;
        prevSize = 0;
        prevElementSize = 0;
        offsetOnVU = 0;
        for (i=0; i<((int)(CL_MAXCL + 2)); i++)
        {
            if (pvtdata->clinfo[i].attrib  & CL_ATTRIB_REQUIRED)
            {
                pvtdata->triStrip.fieldRec[i].vuoffset = offsetOnVU;
                if (pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE)
                {
                    int batchSize
                        = pvtdata->triStrip.fieldRec[i].morphNumVerts
                        = pvtdata->triStrip.morphBatchSize;
                    dataTmp += prevSize + 1;
                    pvtdata->triStrip.fieldRec[i].morphDataoffset = dataTmp;
                    pvtdata->triStrip.fieldRec[i].morphSkip = opaqueStep;
                    pvtdata->triStrip.fieldRec[i].reverse = 0;
                    offsetOnVU += 1; /* size on vu */
                    switch (i)
                    {
                        case CL_XYZ:
                            prevSize = (3*batchSize+3)>>2;
                            prevElementSize = 12;
                            break;
                        case CL_UV:
                            prevSize = (2*batchSize+3)>>2;
                            prevElementSize = 8;
                            break;
                        case CL_UV2:
                            prevSize = (4*batchSize+3)>>2;
                            prevElementSize = 16;
                            break;
                        case CL_RGBA:
                            prevSize = (batchSize+3)>>2;
                            prevElementSize = 4;
                            break;
                        case CL_NORMAL:
                            prevSize = (3*batchSize+15)>>4;
                            prevElementSize = 3;
                            break;
                        /* Fakes */
                        case CL_MAXCL:
                            prevSize = (3*batchSize+3)>>2;
                            prevElementSize = 12;
                            break;
                        case CL_MAXCL+1:
                            prevSize = (3*batchSize+15)>>4;
                            prevElementSize = 3;
                            break;
                    }
                }
                else
                {
                    pvtdata->triStrip.fieldRec[i].morphNumVerts = 0;
                    pvtdata->triStrip.fieldRec[i].morphDataoffset = 0;
                    pvtdata->triStrip.fieldRec[i].morphSkip = 0;
                    pvtdata->triStrip.fieldRec[i].reverse = 0;
                    offsetOnVU += 1; /* size on vu */
                }
            }
            else if (pvtdata->clinfo[i].attrib  & CL_ATTRIB_PLACEHOLDER)
            {
                pvtdata->triStrip.fieldRec[i].vuoffset = offsetOnVU;
                pvtdata->triStrip.fieldRec[i].morphNumVerts = 0;
                pvtdata->triStrip.fieldRec[i].morphDataoffset = 0;
                pvtdata->triStrip.fieldRec[i].morphSkip = 0;
                pvtdata->triStrip.fieldRec[i].reverse = 0;
                offsetOnVU += 1; /* size on vu */
            }
        }
#endif /* FASTMORPH */
    }

    /* We now figure out what the opaque step is for trilists */
    if (totallyOpaque)
    {
        opaqueStep = 1;
    }
    else
    {
        opaqueStep = 2;
    }
    numStripes = 0;
    for (i=0; i<((int)(CL_MAXCL)); i++)
    {
        if (pvtdata->clinfo[i].attrib  & CL_ATTRIB_REQUIRED)
        {
            if (pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE)
            {
                RWASSERT((i == CL_XYZ)  || (i == CL_UV) || (i == CL_UV2) ||
                         (i == CL_RGBA) || (i == CL_NORMAL));
                /* Per batch size is complicated by the fact the */
                /* each batch is not a whole multiple of qw */
                switch (i)
                {
                    case CL_XYZ:
                        opaqueStep += (3*pvtdata->triList.batchSize+3)>>2;
                        break;
                    case CL_UV:
                        opaqueStep += (2*pvtdata->triList.batchSize+3)>>2;
                        break;
                    case CL_UV2:
                        opaqueStep += (4*pvtdata->triList.batchSize+3)>>2;
                        break;
                    case CL_RGBA:
                        opaqueStep += (pvtdata->triList.batchSize+3)>>2;
                        break;
                    case CL_NORMAL:
                        opaqueStep += (3*pvtdata->triList.batchSize+15)>>4;
                        break;
                }
                /* + vif tag */
                opaqueStep +=1;
            }
            else
            {
                /* Dma ref and Dma vnt 0 */
                opaqueStep += 2;
                numStripes += 1;
            }
        }
    }
    /* Save for future reference */
    pvtdata->numStripes = numStripes;

    /* Opaque data starts 2*numStripes+1 qw from data */
    /* each following opaque cluster starts 1+prev size */
    dataTmp = 0 + 2*numStripes+1;
    prevSize = 0;
    prevElementSize = 0;
    offsetOnVU = 0;
    for (i=0; i<((int)(CL_MAXCL)); i++)
    {
        if (pvtdata->clinfo[i].attrib  & CL_ATTRIB_REQUIRED)
        {
            pvtdata->triList.fieldRec[i].vuoffset = offsetOnVU;
            if (pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE)
            {
                int batchSize
                    = pvtdata->triList.fieldRec[i].numVerts
                    = pvtdata->triList.batchSize;
                dataTmp += prevSize + 1;
                pvtdata->triList.fieldRec[i].dataoffset = dataTmp;
                pvtdata->triList.fieldRec[i].skip = opaqueStep;
                pvtdata->triList.fieldRec[i].reverse = 0;
                offsetOnVU += 1; /* size on vu */
                switch (i)
                {
                    case CL_XYZ:
                        prevSize = (3*batchSize+3)>>2;
                        prevElementSize = 12;
                        break;
                    case CL_UV:
                        prevSize = (2*batchSize+3)>>2;
                        prevElementSize = 8;
                        break;
                    case CL_UV2:
                        prevSize = (4*batchSize+3)>>2;
                        prevElementSize = 16;
                        break;
                    case CL_RGBA:
                        prevSize = (batchSize+3)>>2;
                        prevElementSize = 4;
                        break;
                    case CL_NORMAL:
                        prevSize = (3*batchSize+15)>>4;
                        prevElementSize = 3;
                        break;
                }
            }
            else
            {
                pvtdata->triList.fieldRec[i].numVerts = 0;
                pvtdata->triList.fieldRec[i].dataoffset = 0;
                pvtdata->triList.fieldRec[i].skip = 0;
                pvtdata->triList.fieldRec[i].reverse = 0;
                offsetOnVU += 1; /* size on vu */
            }
        }
    }
#ifdef FASTMORPH
    if (totallyOpaque)
    {
        opaqueStep = 1;
    }
    else
    {
        opaqueStep = 2;
    }
    numStripes = 0;
    for (i=0; i<((int)(CL_MAXCL + 2)); i++)
    {
        if (pvtdata->clinfo[i].attrib  & CL_ATTRIB_REQUIRED)
        {
            if (pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE)
            {
                RWASSERT((i == CL_XYZ)  || (i == CL_UV) ||  (i == CL_UV2) ||
                         (i == CL_RGBA) || (i == CL_NORMAL) ||
                         (i == CL_MAXCL) || (i == CL_MAXCL+1));
                /* Per batch size is complicated by the fact the */
                /* each batch is not a whole multiple of qw */
                switch (i)
                {
                    case CL_XYZ:
                        opaqueStep += (3*pvtdata->triList.morphBatchSize+3)>>2;
                        break;
                    case CL_UV:
                        opaqueStep += (2*pvtdata->triList.morphBatchSize+3)>>2;
                        break;
                    case CL_UV2:
                        opaqueStep += (4*pvtdata->triList.morphBatchSize+3)>>2;
                        break;
                    case CL_RGBA:
                        opaqueStep += (pvtdata->triList.morphBatchSize+3)>>2;
                        break;
                    case CL_NORMAL:
                        opaqueStep += (3*pvtdata->triList.morphBatchSize+15)>>4;
                        break;
                    case CL_MAXCL:
                        opaqueStep += (3*pvtdata->triList.morphBatchSize+3)>>2;
                        break;
                    case CL_MAXCL+1:
                        opaqueStep += (3*pvtdata->triList.morphBatchSize+15)>>4;
                        break;
                }
                /* + vif tag */
                opaqueStep +=1;
            }
            else
            {
                /* Dma ref and Dma vnt 0 */
                opaqueStep += 2;
                numStripes += 1;
            }
        }
    }
    /* Save for future reference */
    pvtdata->numStripes = numStripes;

    /* Opaque data starts 2*numStripes+1 qw from data */
    /* each following opaque cluster starts 1+prev size */
    dataTmp = 0 + 2*numStripes+1;
    prevSize = 0;
    prevElementSize = 0;
    offsetOnVU = 0;
    for (i=0; i<((int)(CL_MAXCL + 2)); i++)
    {
        if (pvtdata->clinfo[i].attrib  & CL_ATTRIB_REQUIRED)
        {
            /* We don't need morphOffsetOnVU because this will only make a
             * difference to the FASTMORPH clusters (>= MAX_CL) */
            pvtdata->triList.fieldRec[i].vuoffset = offsetOnVU;
            if (pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE)
            {
                int batchSize
                    = pvtdata->triList.fieldRec[i].morphNumVerts
                    = pvtdata->triList.morphBatchSize;
                dataTmp += prevSize + 1;
                pvtdata->triList.fieldRec[i].morphDataoffset = dataTmp;
                pvtdata->triList.fieldRec[i].morphSkip = opaqueStep;
                /* This unnecessarily repeats reverse initialisation,
                 * I don't think it's harmful though. */
                pvtdata->triList.fieldRec[i].reverse = 0;
                offsetOnVU += 1; /* size on vu */
                switch (i)
                {
                    case CL_XYZ:
                        prevSize = (3*batchSize+3)>>2;
                        prevElementSize = 12;
                        break;
                    case CL_UV:
                        prevSize = (2*batchSize+3)>>2;
                        prevElementSize = 8;
                        break;
                    case CL_UV2:
                        prevSize = (4*batchSize+3)>>2;
                        prevElementSize = 16;
                        break;
                    case CL_RGBA:
                        prevSize = (batchSize+3)>>2;
                        prevElementSize = 4;
                        break;
                    case CL_NORMAL:
                        prevSize = (3*batchSize+15)>>4;
                        prevElementSize = 3;
                        break;
                    case CL_MAXCL:
                        prevSize = (3*batchSize+3)>>2;
                        prevElementSize = 12;
                        break;
                    case CL_MAXCL+1:
                        prevSize = (3*batchSize+15)>>4;
                        prevElementSize = 3;
                        break;
                }
            }
            else
            {
                pvtdata->triList.fieldRec[i].morphNumVerts = 0;
                pvtdata->triList.fieldRec[i].morphDataoffset = 0;
                pvtdata->triList.fieldRec[i].morphSkip = 0;
                /* This unnecessarily repeats reverse initialisation,
                 * I don't think it's harmful though. */
                pvtdata->triList.fieldRec[i].reverse = 0;
                offsetOnVU += 1; /* size on vu */
            }
        }
    }
#endif /* FASTMORPH */

#ifdef REDEBUG
    /* dump pvtdata to stdout */
    {
        int i,j;
        int ze = 0;
        volatile int *vol = &ze;

        printf("rwPS2MatInstancePvtData generated by RabinsConstructionTimeCode:\n");
        /*printf(" breakoutsbitfield = %08x\n", pvtdata->breakoutsbitfield);*/
        printf(" totallyOpaque = %d\n", pvtdata->totallyOpaque);
        printf(" numStripes = %d\n", pvtdata->numStripes);
        printf(" sizeOnVU = %d\n", pvtdata->sizeOnVU);
        printf(" clinfo:\n");
        for (i = 0; i< ((int)(CL_MAXCL)); i++)
        {
            printf("  Cluster %d\n", i);
            /*printf("  cliIndex %d\n", pvtdata->clinfo[i].cliIndex);*/
            printf("  attrib %08x\n", pvtdata->clinfo[i].attrib);
            /*printf("  cluster %p\n", pvtdata->clinfo[i].cluster);*/
            /* We put a pause in here as dsnetm has a buffer overrun problem */
            for (j=0; j<100000; j++)
            {
                if (*vol)
                {
                    printf("Odd?\n");
                }
            }
        }
        if (pointList == FALSE);
        {
            printf(" triStrip:\n");
            printf("  batchSize %d\n", pvtdata->triStrip.batchSize);
            printf("  batchesPerTag %d\n", pvtdata->triStrip.batchesPerTag);
#ifdef FASTMORPH
            printf("  morphBatchSize %d\n", pvtdata->triStrip.morphBatchSize);
            printf("  morphBatchesPerTag %d\n", pvtdata->triStrip.morphBatchesPerTag);
#endif /* FASTMORPH */
#ifndef FASTMORPH
            for (i = 0; i< ((int)(CL_MAXCL)); i++)
#else /* !FASTMORPH */
            for (i = 0; i< ((int)(CL_MAXCL + 2)); i++)
#endif /* !FASTMORPH */
            {
                printf("   fieldRec %d\n", i);
                printf("   numVerts %d\n", pvtdata->triStrip.fieldRec[i].numVerts);
#ifdef FASTMORPH
                printf("   morphNumVerts %d\n", pvtdata->triStrip.fieldRec[i].morphNumVerts);
#endif /* FASTMORPH */
                printf("   dataoffset %d\n", pvtdata->triStrip.fieldRec[i].dataoffset);
#ifdef FASTMORPH
                printf("   morphDataoffset %d\n", pvtdata->triStrip.fieldRec[i].morphDataoffset);
#endif /* FASTMORPH */
                printf("   skip %d\n", pvtdata->triStrip.fieldRec[i].skip);
#ifdef FASTMORPH
                printf("   morphSkip %d\n", pvtdata->triStrip.fieldRec[i].morphSkip);
#endif /* FASTMORPH */
                printf("   vuoffset %d\n", pvtdata->triStrip.fieldRec[i].vuoffset);
                /* We put a pause in here as dsnetm has a buffer overrun problem */
                for (j=0; j<100000; j++)
                {
                    if (*vol)
                    {
                        printf("Odd?\n");
                    }
                }
            }
        }

        if (pointList == FALSE)
        {
            printf(" triList:\n");
        }
        else
        {
            printf(" pointList:\n");
        }
        printf("  batchSize %d\n", pvtdata->triList.batchSize);
        printf("  batchesPerTag %d\n", pvtdata->triList.batchesPerTag);
#ifdef FASTMORPH
        printf("  morphBatchSize %d\n", pvtdata->triList.morphBatchSize);
        printf("  morphBatchesPerTag %d\n", pvtdata->triList.morphBatchesPerTag);
#endif /* FASTMORPH */
#ifndef FASTMORPH
        for (i = 0; i< ((int)(CL_MAXCL)); i++)
#else /* !FASTMORPH */
        for (i = 0; i< ((int)(CL_MAXCL + 2)); i++)
#endif /* !FASTMORPH */
        {
            printf("   fieldRec %d\n", i);
            printf("   numVerts %d\n", pvtdata->triList.fieldRec[i].numVerts);
#ifdef FASTMORPH
            printf("   morphNumVerts %d\n", pvtdata->triList.fieldRec[i].morphNumVerts);
#endif /* FASTMORPH */
            printf("   dataoffset %d\n", pvtdata->triList.fieldRec[i].dataoffset);
#ifdef FASTMORPH
            printf("   morphDataoffset %d\n", pvtdata->triList.fieldRec[i].morphDataoffset);
#endif /* FASTMORPH */
            printf("   skip %d\n", pvtdata->triList.fieldRec[i].skip);
#ifdef FASTMORPH
            printf("   morphSkip %d\n", pvtdata->triList.fieldRec[i].morphSkip);
#endif /* FASTMORPH */
            printf("   vuoffset %d\n", pvtdata->triList.fieldRec[i].vuoffset);
            /* We put a pause in here as dsnetm has a buffer overrun problem */
            for (j=0; j<100000; j++)
            {
                if (*vol)
                {
                    printf("Odd?\n");
                }
            }
        }
    }
#endif /* REDEBUG */

    result = TRUE; /* success */

    RWRETURN(result);
}

/****************************************************************************
 reInstanceFromIm3D()
 */
static RwBool
reInstanceFromIm3D(rwPS2ResEntryHeader *ps2ResHeader, const RpMesh *mesh,
                   RpMeshHeaderFlags flags,
                   RwInt32 numVerts,
                   const RxPS2DMASessionRecord *DMASessionRecord)
{
    _rwIm3DPoolStash *stash =
        (_rwIm3DPoolStash *)DMASessionRecord->sourceObject.agnostic;
    RwUInt32 stripTmp;
    /* These next two MUST be signed or they'll wrap around causing infinite loops!! */
    RwInt32 vertCounter, j;
    INDEXDECLARE();

    RWFUNCTION(RWSTRING("reInstanceFromIm3D"));

#ifdef REDEBUG
    printf("numVerts: %d\n", numVerts);
    printf("batchSize: %d\n", ps2ResHeader->batchSize);
#endif /* REDEBUG */

    stripTmp = (flags&rpMESHHEADERTRISTRIP) ? 2 : 0;

    /* Initialise the indexing LUTs dependant on the
     * primtype and the presence/absence of indices */
    INDEXSETUP(flags, mesh->indices);

    /* XYZs are no longer automatic. We could be render private immediate data, eg
     * Rt2d.
     */
    if (stash->flags & rwIM3D_VERTEXXYZ)
    {
        u_long128 *data =
            (ps2ResHeader->data + ps2ResHeader->fieldRec[CL_XYZ].dataoffset);
        RwReal *dataTmp = (RwReal *)data;
        RxObjSpace3DLitVertex *verts = stash->objVerts;

        INDEXRESET();

        /* If we're converting from trifans to trilists or polylines to linelists
         * then the number of source and dest verts per batch will differ. Our
         * vertCounter counts down destination verts (with reversal for tristrip
         * vert duplications between batches), whilst in the inner loop we generate
         * dest verts indexing into the source verts/indices arrays. */
        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
            {
                /* The last batch, shuffle it back to fit snugly after
                 * the (smaller) final batches of prior clusters */
                data -= ps2ResHeader->fieldRec[CL_XYZ].reverse;
                dataTmp = (RwReal *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                /* This is batchSize if this cluster is opaque, else it
                 * really is numVerts (total in mesh - excluding tristrip
                 * duplications - cos broken-out clusters are contiguous
                 * so we can do 'em all in one go) */
                j = ps2ResHeader->fieldRec[CL_XYZ].numVerts;
                /* Note that next relies on fieldRec[].numVerts NOT
                 * including the repeated tristrip verts */
                vertCounter -= (j - stripTmp);
            }

            while (j--)
            {
                RwV3d pos;
                RxVertexIndex idx = INDEXGET();

                RxObjSpace3DVertexGetPos(&(verts[idx]), &pos);
               *dataTmp++ = pos.x;
               *dataTmp++ = pos.y;
               *dataTmp++ = pos.z;

                INDEXINC();
            }

            /* Take into account duplication for strips split across batches */
            STRIPREVERSE(stripTmp);

            /* Skip past DMA tags and blocks of other clusters' data */
            data += ps2ResHeader->fieldRec[CL_XYZ].skip;
            dataTmp = (RwReal *)data;
        }
    }

    /* TODO: should have two pipes, one for textured and one for
     *        untextured Im3D! */
    if (stash->flags & rwIM3D_VERTEXUV)
    {
        u_long128 *data =
            (ps2ResHeader->data + ps2ResHeader->fieldRec[CL_UV].dataoffset);
        RwReal *dataTmp = (RwReal *)data;
        RxObjSpace3DLitVertex *verts = stash->objVerts;

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
            {
                data -= ps2ResHeader->fieldRec[CL_UV].reverse;
                dataTmp = (RwReal *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2ResHeader->fieldRec[CL_UV].numVerts;
                vertCounter -= (j - stripTmp);
            }

            while (j--)
            {
                RxVertexIndex idx = INDEXGET();
                RxObjSpace3DLitVertex *vert = &(verts[idx]);

               *dataTmp++ = RxObjSpace3DVertexGetU(vert);
               *dataTmp++ = RxObjSpace3DVertexGetV(vert);

                INDEXINC();
            }

            STRIPREVERSE(stripTmp);

            data += ps2ResHeader->fieldRec[CL_UV].skip;
            dataTmp = (RwReal *)data;
        }
    }

    /* RGBAs are no longer automatic. We could be render private immediate data, eg
     * Rt2d.
     */
    if (stash->flags & rwIM3D_VERTEXRGBA)
    {
        u_long128 *data =
            (ps2ResHeader->data + ps2ResHeader->fieldRec[CL_RGBA].dataoffset);
        RwUInt32 *dataTmp = (RwUInt32 *)data;
        RxObjSpace3DLitVertex *verts = stash->objVerts;

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
            {
                data -= ps2ResHeader->fieldRec[CL_RGBA].reverse;
                dataTmp = (RwUInt32 *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2ResHeader->fieldRec[CL_RGBA].numVerts;
                vertCounter -= (j - stripTmp);
            }

            while (j--)
            {
#if (!defined(OVERRIDELIGHT))
                RxVertexIndex idx = INDEXGET();
                RwRGBA col;

                RxObjSpace3DVertexGetColor(&(verts[idx]), &col);

                *dataTmp++ = (RwUInt32)((RwUInt8)col.red)
                    |((RwUInt32)((RwUInt8)col.green)<<8)
                    |((RwUInt32)((RwUInt8)col.blue)<<16)
                    |((RwUInt32)((RwUInt8)col.alpha)<<24);

                INDEXINC();

#else /* !OVERRIDELIGHT */
                *dataTmp++ = 0xffffffff;
#endif /* !OVERRIDELIGHT */
            }

            STRIPREVERSE(stripTmp);

            data += ps2ResHeader->fieldRec[CL_RGBA].skip;
            dataTmp = (RwUInt32 *)data;
        }
    }

#ifdef REDEBUG
#ifdef DMADUMPDATA
    /* dump the entire structure to stdout */
    {
        int j;
        int ze = 0;
        volatile int *vol = &ze;
        u_long128* ptr;

        /* Now dump the data */
        ptr = ps2ResHeader->data;
        for (;(int)ptr < (int)((char*)ps2ResHeader
                               +((RwResEntry*)ps2ResHeader-1)->size); ptr++)
        {
            printf("%x: %.16lx %.16lx\n", (int)ptr, *((long*)ptr+1), *(long*)ptr);
            /* We put a pause in here as dsnetm has a buffer overrun problem */
            for (j=0; j<100000; j++)
            {
                if (*vol)
                {
                    printf("Odd?\n");
                }
            }

        }
    }
#endif /* DMADUMPDATA */
#endif /* REDEBUG */

    RWRETURN(TRUE);
}

/****************************************************************************
 */
static RwBool
reInstanceFromWorldSector(rwPS2MatInstancePvtData *pvtdata,
                          rwPS2ResEntryHeader *ps2ResHeader,
                          const RpMesh *mesh,
                          RpMeshHeaderFlags flags,
                          RwUInt32 numVerts,
                          const RxPS2DMASessionRecord *DMASessionRecord)
{
    RpWorld *world = (RpWorld *)RWSRCGLOBAL(curWorld);
    /* These next two MUST be signed or they'll wrap around causing infinite loops!! */
    RwInt32 vertCounter, j;
    RwUInt32 stripTmp;
    INDEXDECLARE();

    RWFUNCTION(RWSTRING("reInstanceFromWorldSector"));

    stripTmp = (flags&rpMESHHEADERTRISTRIP) ? 2 : 0;

    INDEXSETUP(flags, mesh->indices);


    /* We attempt to instance the required data */
    if ((  pvtdata->clinfo[CL_XYZ].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(pvtdata->clinfo[CL_XYZ].attrib & CL_ATTRIB_DONT_FILL))   )
    {
        u_long128 *data = (ps2ResHeader->data
                           + ps2ResHeader->fieldRec[CL_XYZ].dataoffset);
        RwReal *dataTmp = (RwReal *)data;
        RwV3d *vertices = DMASessionRecord->sourceObject.worldSector->vertices;

        /*RWASSERT(rwObjectTestFlags(world, rpWORLDPOSITIONS));*/

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
            {
                data -= ps2ResHeader->fieldRec[CL_XYZ].reverse;
                dataTmp = (RwReal *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2ResHeader->fieldRec[CL_XYZ].numVerts;
                vertCounter -= (j - stripTmp);
            }

            while (j--)
            {
                RxVertexIndex idx = INDEXGET();
                RwV3d        *pos = &(vertices[idx]);

               *dataTmp++ = pos->x;
               *dataTmp++ = pos->y;
               *dataTmp++ = pos->z;

                INDEXINC();
            }

            STRIPREVERSE(stripTmp);

            data += ps2ResHeader->fieldRec[CL_XYZ].skip;
            dataTmp = (RwReal *)data;
        }
    }

    /* NOTE: we *should* treat trying to get UVs from an untextured object as
     * an error and assert on it, but realistically people (especially our
     * examples!) will throw textured and untextured objects at the same
     * default pipelines and expect them to work... :o/ */
    if ((  pvtdata->clinfo[CL_UV].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(pvtdata->clinfo[CL_UV].attrib & CL_ATTRIB_DONT_FILL)) &&
        rwObjectTestFlags(world, rpWORLDTEXTURED | rpWORLDTEXTURED2) )
    {
        u_long128 *data =
            (ps2ResHeader->data + ps2ResHeader->fieldRec[CL_UV].dataoffset);
        RwReal *dataTmp = (RwReal *)data;
        RwTexCoords *tc =
            DMASessionRecord->sourceObject.worldSector->texCoords[0];

        /* RWASSERT(rwObjectTestFlags(world, rpWORLDTEXTURED ) ||
                 rwObjectTestFlags(world, rpWORLDTEXTURED2)   ); */

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
            {
                data -= ps2ResHeader->fieldRec[CL_UV].reverse;
                dataTmp = (RwReal *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2ResHeader->fieldRec[CL_UV].numVerts;
                vertCounter -= (j - stripTmp);
            }

            while (j--)
            {
                RxVertexIndex idx   = INDEXGET();
                RwTexCoords  *coord = &(tc[idx]);

               *dataTmp++ = coord->u;
               *dataTmp++ = coord->v;

                INDEXINC();
            }

            STRIPREVERSE(stripTmp);

            data += ps2ResHeader->fieldRec[CL_UV].skip;
            dataTmp = (RwReal *)data;
        }
    }

    if ((  pvtdata->clinfo[CL_UV2].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(pvtdata->clinfo[CL_UV2].attrib & CL_ATTRIB_DONT_FILL)) &&
        rwObjectTestFlags(world, rpWORLDTEXTURED2))
    {
        u_long128 *data =
            (ps2ResHeader->data + ps2ResHeader->fieldRec[CL_UV2].dataoffset);
        RwReal *dataTmp = (RwReal *)data;
        RwTexCoords *tc1 =
            DMASessionRecord->sourceObject.worldSector->texCoords[0];
        RwTexCoords *tc2 =
            DMASessionRecord->sourceObject.worldSector->texCoords[1];

        /* RWASSERT(rwObjectTestFlags(world, rpWORLDTEXTURED2)); */

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
            {
                data -= ps2ResHeader->fieldRec[CL_UV2].reverse;
                dataTmp = (RwReal *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2ResHeader->fieldRec[CL_UV2].numVerts;
                vertCounter -= (j - stripTmp);
            }

            while (j--)
            {
                RxVertexIndex idx   = INDEXGET();
                RwTexCoords  *coord;

                coord = &(tc1[idx]);
                *dataTmp++ = coord->u;
                *dataTmp++ = coord->v;

                coord = &(tc2[idx]);
                *dataTmp++ = coord->u;
                *dataTmp++ = coord->v;

                INDEXINC();
            }

            STRIPREVERSE(stripTmp);

            data += ps2ResHeader->fieldRec[CL_UV2].skip;
            dataTmp = (RwReal *)data;
        }
    }

    if ((  pvtdata->clinfo[CL_RGBA].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(pvtdata->clinfo[CL_RGBA].attrib & CL_ATTRIB_DONT_FILL))   )
    {
        u_long128 *data =
            (ps2ResHeader->data + ps2ResHeader->fieldRec[CL_RGBA].dataoffset);
        RwUInt32 *dataTmp = (RwUInt32*)data;
        RwRGBA *preLitLum =
            DMASessionRecord->sourceObject.worldSector->preLitLum;

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
            {
                data -= ps2ResHeader->fieldRec[CL_RGBA].reverse;
                dataTmp = (RwUInt32 *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2ResHeader->fieldRec[CL_RGBA].numVerts;
                vertCounter -= (j - stripTmp);
            }

            if (rwObjectTestFlags(world, rpWORLDPRELIT))
            {
                while (j--)
                {
#if (!defined(OVERRIDELIGHT))
                    RxVertexIndex idx = INDEXGET();
                    const RwRGBA *col = &(preLitLum[idx]);

                   *dataTmp++ = ((RwUInt32)((RwUInt8)col->red  ) <<  0) |
                                ((RwUInt32)((RwUInt8)col->green) <<  8) |
                                ((RwUInt32)((RwUInt8)col->blue ) << 16) |
                                ((RwUInt32)((RwUInt8)col->alpha) << 24);
                    INDEXINC();
#else /* !OVERRIDELIGHT */
                   *dataTmp++ = 0xffffffff;
#endif /* !OVERRIDELIGHT */
                }
            }
            else
            {
                while (j--)
                {
#if (!defined(SUBSISTLIGHT))
                    *dataTmp++ = 0xff000000;
#else /* !SUBSISTLIGHT */
                    *dataTmp++ = 0xffffffff;
#endif /* !SUBSISTLIGHT */
                }
            }

            STRIPREVERSE(stripTmp);

            data += ps2ResHeader->fieldRec[CL_RGBA].skip;
            dataTmp = (RwUInt32 *)data;
        }
    }

    if ((  pvtdata->clinfo[CL_NORMAL].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(pvtdata->clinfo[CL_NORMAL].attrib & CL_ATTRIB_DONT_FILL))   )
    {
        u_long128 *data = (ps2ResHeader->data
                           + ps2ResHeader->fieldRec[CL_NORMAL].dataoffset);
        RwUInt8 *dataTmp = (RwUInt8 *)data;
        RpVertexNormal *normals =
            DMASessionRecord->sourceObject.worldSector->normals;

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
            {
                data -= ps2ResHeader->fieldRec[CL_NORMAL].reverse;
                dataTmp = (RwUInt8 *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2ResHeader->fieldRec[CL_NORMAL].numVerts;
                vertCounter -= (j - stripTmp);
            }

            if (rwObjectTestFlags(world, rpWORLDNORMALS))
            {
                if (pvtdata->clinfo[CL_NORMAL].attrib & CL_ATTRIB_OPAQUE)
                {
                    while (j--)
                    {
                        RwUInt32        idx    = INDEXGET();
                        RpVertexNormal *normal = &(normals[idx]);

                       *dataTmp++ = normal->x;
                       *dataTmp++ = normal->y;
                       *dataTmp++ = normal->z;

                        INDEXINC();
                    }
                }
                else
                {
                    while (j--)
                    {
                        RwUInt32        idx    = INDEXGET();
                        RpVertexNormal *normal = &(normals[idx]);

                       *dataTmp++ = normal->x;
                       *dataTmp++ = normal->y;
                       *dataTmp++ = normal->z;
                       *dataTmp++ = 0;

                        INDEXINC();
                    }
                }
            }
            else
            {
                if (pvtdata->clinfo[CL_NORMAL].attrib & CL_ATTRIB_OPAQUE)
                {
                    while (j--)
                    {
                       *dataTmp++ = 0;
                       *dataTmp++ = 0;
                       *dataTmp++ = 0;
                    }
                }
                else
                {
                    while (j--)
                    {
                       *dataTmp++ = 0;
                       *dataTmp++ = 0;
                       *dataTmp++ = 0;
                       *dataTmp++ = 0;
                    }
                }
            }

            STRIPREVERSE(stripTmp);

            data += ps2ResHeader->fieldRec[CL_NORMAL].skip;
            dataTmp = (RwUInt8 *)data;
        }
    }

#ifdef REDEBUG
#ifdef DMADUMPDATA
    /* dump the entire structure to stdout */
    {
        int j;
        int ze = 0;
        volatile int *vol = &ze;
        u_long128* ptr;

        /* Now dump the data */
        ptr = ps2ResHeader->data;
        for (;(int)ptr < (int)((char*)ps2ResHeader
                               +((RwResEntry*)ps2ResHeader-1)->size); ptr++)
        {
            printf("%x: %.16lx %.16lx\n", (int)ptr, *((long*)ptr+1), *(long*)ptr);
            /* We put a pause in here as dsnetm has a buffer overrun problem */
            for (j=0; j<100000; j++)
            {
                if (*vol)
                {
                    printf("Odd?\n");
                }
            }

        }
    }
#endif /* DMADUMPDATA */
#endif /* REDEBUG */

    RWRETURN(TRUE);
}

/****************************************************************************
 */
static RwBool
reInstanceFromAtomic(rwPS2MatInstancePvtData *pvtdata,
                     rwPS2ResEntryHeader *ps2ResHeader, const RpMesh *mesh,
                     RpMeshHeaderFlags flags,
                     RwUInt32 numVerts,
                     RwUInt16 needToReInstance,
                     const RxPS2DMASessionRecord *DMASessionRecord)
{
    RpGeometry *geom
        = RpAtomicGetGeometry(DMASessionRecord->sourceObject.atomic);
    RpInterpolator *interpolator
        = &DMASessionRecord->sourceObject.atomic->interpolator;
    /* These next two MUST be signed or they'll wrap around causing infinite loops!! */
    RwInt32 vertCounter, j;
    RwUInt32 stripTmp;
    RwBool instanceXYZ, instanceNormal;
    RpMorphTarget *startMorphTarget, *endMorphTarget;
#ifndef FASTMORPH
    RwReal scale;
#endif /* !FASTMORPH */
    INDEXDECLARE();

    RWFUNCTION(RWSTRING("reInstanceFromAtomic"));

#ifdef REDEBUG
    printf("reInstanceFromAtomic");
    printf("numVerts: %d\n", numVerts);
    printf("batchSize: %d\n", ps2ResHeader->batchSize);
#endif /* REDEBUG */

    stripTmp = (flags&rpMESHHEADERTRISTRIP) ? 2 : 0;

    INDEXSETUP(flags, mesh->indices);


    /* We attempt to instance the required data */
    /* XYZs and Normals are painful in atomics */

    instanceXYZ = FALSE;
    if ((  pvtdata->clinfo[CL_XYZ].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(pvtdata->clinfo[CL_XYZ].attrib & CL_ATTRIB_DONT_FILL)) &&
        (needToReInstance & (rpGEOMETRYLOCKVERTICES|rpGEOMETRYLOCKPOLYGONS)))
    {
        /* RWASSERT(rwObjectTestFlags(geom, rpGEOMETRYPOSITIONS)); */
        instanceXYZ = TRUE;
    }

    instanceNormal = FALSE;
    if  ((  pvtdata->clinfo[CL_NORMAL].attrib & CL_ATTRIB_REQUIRED  ) &&
         (!(pvtdata->clinfo[CL_NORMAL].attrib & CL_ATTRIB_DONT_FILL)) &&
         (needToReInstance & (rpGEOMETRYLOCKNORMALS|rpGEOMETRYLOCKPOLYGONS)))
    {
        /* If normals are absent, we fill with {0, 0, 0} */
        instanceNormal = TRUE;
    }

    /* Test for invalid morph target indices */
    RWASSERT(interpolator->startMorphTarget < geom->numMorphTargets);
    RWASSERT(interpolator->endMorphTarget   < geom->numMorphTargets);

#ifndef FASTMORPH
    if (interpolator->startMorphTarget == interpolator->endMorphTarget)
#endif /* FASTMORPH */
    {
        /* Expand a single morph target. Both morph targets must
         * be the same, so grab one or the other */
        startMorphTarget =
            &geom->morphTarget[interpolator->startMorphTarget];

        if (FALSE != instanceXYZ)
        {
            u_long128 *data;
            RwReal *dataTmp;
            RwV3d *vertices = startMorphTarget->verts;

            RWASSERT(NULL != vertices);

            data = ps2ResHeader->data +
                   ps2ResHeader->fieldRec[CL_XYZ].dataoffset;
#ifdef FASTMORPH
            if (geom->numMorphTargets > 1)
            {
                data = ps2ResHeader->data +
                       ps2ResHeader->fieldRec[CL_XYZ].morphDataoffset;
            }
#endif /* FASTMORPH */
            dataTmp = (RwReal *)data;

            INDEXRESET();

            vertCounter = numVerts;
            while (vertCounter > 0)
            {
                if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
                {
                    data -= ps2ResHeader->fieldRec[CL_XYZ].reverse;
                    dataTmp = (RwReal *)data;
                    j = vertCounter;
                    vertCounter = 0;
                }
                else
                {
                    j = ps2ResHeader->fieldRec[CL_XYZ].numVerts;
#ifdef FASTMORPH
                    if (geom->numMorphTargets > 1)
                    {
                        j = ps2ResHeader->fieldRec[CL_XYZ].morphNumVerts;
                    }
#endif /* FASTMORPH */
                    vertCounter -= (j - stripTmp);
                }

                while(j--)
                {
                    RxVertexIndex idx = INDEXGET();
                    RwV3d        *pos = &(vertices[idx]);

                   *dataTmp++ = pos->x;
                   *dataTmp++ = pos->y;
                   *dataTmp++ = pos->z;

                    INDEXINC();
                }

                STRIPREVERSE(stripTmp);

                j = ps2ResHeader->fieldRec[CL_XYZ].skip;
#ifdef FASTMORPH
                if (geom->numMorphTargets > 1)
                {
                    j = ps2ResHeader->fieldRec[CL_XYZ].morphSkip;
                }
#endif /* FASTMORPH */
                data += j;
                dataTmp = (RwReal *)data;
            }
        }

        if (FALSE != instanceNormal)
        {
            u_long128 *data;
            RwUInt8 *dataTmp;
            RwV3d   *normals = startMorphTarget->normals;

            data = ps2ResHeader->data +
                   ps2ResHeader->fieldRec[CL_NORMAL].dataoffset;
#ifdef FASTMORPH
            if (geom->numMorphTargets > 1)
            {
                data = ps2ResHeader->data +
                       ps2ResHeader->fieldRec[CL_NORMAL].morphDataoffset;
            }
#endif /* FASTMORPH */
            dataTmp = (RwUInt8 *)data;

            INDEXRESET();

            vertCounter = numVerts;
            while (vertCounter > 0)
            {
                if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
                {
                    data -= ps2ResHeader->fieldRec[CL_NORMAL].reverse;
                    dataTmp = (RwUInt8 *)data;
                    j = vertCounter;
                    vertCounter = 0;
                }
                else
                {
                    j = ps2ResHeader->fieldRec[CL_NORMAL].numVerts;
#ifdef FASTMORPH
                    if (geom->numMorphTargets > 1)
                    {
                        j = ps2ResHeader->fieldRec[CL_NORMAL].morphNumVerts;
                    }
#endif /* FASTMORPH */
                    vertCounter -= (j - stripTmp);
                }

                if (rwObjectTestFlags(geom, rpGEOMETRYNORMALS))
                {
                    if (pvtdata->clinfo[CL_NORMAL].attrib&CL_ATTRIB_OPAQUE)
                    {
                        while (j--)
                        {
                            RwUInt32 idx    = INDEXGET();
                            RwV3d   *normal = &(normals[idx]);

                           *dataTmp++ = (RwInt8)(normal->x * 127.0f);
                           *dataTmp++ = (RwInt8)(normal->y * 127.0f);
                           *dataTmp++ = (RwInt8)(normal->z * 127.0f);

                            INDEXINC();
                        }
                    }
                    else
                    {
                        while (j--)
                        {
                            RwUInt32 idx    = INDEXGET();
                            RwV3d   *normal = &(normals[idx]);

                           *dataTmp++ = (RwInt8)(normal->x * 127.0f);
                           *dataTmp++ = (RwInt8)(normal->y * 127.0f);
                           *dataTmp++ = (RwInt8)(normal->z * 127.0f);
                           *dataTmp++ = 0;

                            INDEXINC();
                         }
                    }
                }
                else
                {
                    if (pvtdata->clinfo[CL_NORMAL].attrib&CL_ATTRIB_OPAQUE)
                    {
                        while (j--)
                        {
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                        }
                    }
                    else
                    {
                        while (j--)
                        {
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                        }
                    }
                }

                STRIPREVERSE(stripTmp);

                j= ps2ResHeader->fieldRec[CL_NORMAL].skip;
#ifdef FASTMORPH
                if (geom->numMorphTargets > 1)
                {
                    j = ps2ResHeader->fieldRec[CL_NORMAL].morphSkip;
                }
#endif /* FASTMORPH */
                data += j;
                dataTmp = (RwUInt8 *)data;
            }
        }
    }
#ifndef FASTMORPH
    else
    {
        /* Expand two morph targets */
        startMorphTarget = &geom->morphTarget[interpolator->startMorphTarget];
        endMorphTarget = &geom->morphTarget[interpolator->endMorphTarget];
        scale = ((interpolator->recipTime)*(interpolator->position));

        if (FALSE != instanceXYZ)
        {
            u_long128 *data = (ps2ResHeader->data
                              + ps2ResHeader->fieldRec[CL_XYZ].dataoffset);
            RwReal *dataTmp = (RwReal *)data;
            RwV3d  *startVerts = startMorphTarget->verts;
            RwV3d  *endVerts   = endMorphTarget->verts;

            RWASSERT(NULL != startVerts);
            RWASSERT(NULL != endVerts);

            INDEXRESET();

            vertCounter = numVerts;
            while (vertCounter > 0)
            {
                if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
                {
                    data -= ps2ResHeader->fieldRec[CL_XYZ].reverse;
                    dataTmp = (RwReal *)data;
                    j = vertCounter;
                    vertCounter = 0;
                }
                else
                {
                    j = ps2ResHeader->fieldRec[CL_XYZ].numVerts;
                    vertCounter -= (j - stripTmp);
                }

                while (j--)
                {
                    RxVertexIndex idx      = INDEXGET();
                    RwV3d        *startPos = &(startVerts[idx]);
                    RwV3d        *endPos   = &(endVerts[idx]);

                    RwV3dSub((RwV3d *)dataTmp, endPos, startPos);
                    RwV3dScale((RwV3d *)dataTmp, (RwV3d *)dataTmp, scale);
                    RwV3dAdd((RwV3d *)dataTmp, (RwV3d *)dataTmp, startPos);

                    dataTmp += 3;
                    INDEXINC();
                }

                STRIPREVERSE(stripTmp);

                data += ps2ResHeader->fieldRec[CL_XYZ].skip;
                dataTmp = (RwReal *)data;
            }
        }

        if (FALSE != instanceNormal)
        {
            u_long128 *data = (ps2ResHeader->data
                           + ps2ResHeader->fieldRec[CL_NORMAL].dataoffset);
            RwUInt8 *dataTmp = (RwUInt8 *)data;
            RwV3d *startNormals = startMorphTarget->normals;
            RwV3d *endNormals = endMorphTarget->normals;

            INDEXRESET();

            vertCounter = numVerts;
            while (vertCounter > 0)
            {
                if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
                {
                    data -= ps2ResHeader->fieldRec[CL_NORMAL].reverse;
                    dataTmp = (RwUInt8 *)data;
                    j = vertCounter;
                    vertCounter = 0;
                }
                else
                {
                    j = ps2ResHeader->fieldRec[CL_NORMAL].numVerts;
                    vertCounter -= (j - stripTmp);
                }

                if (rwObjectTestFlags(geom, rpGEOMETRYNORMALS))
                {
                    if (pvtdata->clinfo[CL_NORMAL].attrib&CL_ATTRIB_OPAQUE)
                    {
                        while (j--)
                        {
                            RwV3d    normal;
                            RwUInt32 idx         = INDEXGET();
                            RwV3d   *startNormal = &(startNormals[idx]);
                            RwV3d   *endNormal   = &(endNormals[idx]);

                            RwV3dSub(&normal, endNormal, startNormal);
                            RwV3dScale(&normal, &normal, scale);
                            RwV3dAdd(&normal, &normal, startNormal);
                           *dataTmp++ = (RwInt8)(normal.x * 127.0f);
                           *dataTmp++ = (RwInt8)(normal.y * 127.0f);
                           *dataTmp++ = (RwInt8)(normal.z * 127.0f);

                            INDEXINC();
                        }
                    }
                    else
                    {
                        while (j--)
                        {
                            RwV3d    normal;
                            RwUInt32 idx         = INDEXGET();
                            RwV3d   *startNormal = &(startNormals[idx]);
                            RwV3d   *endNormal   = &(endNormals[idx]);

                            RwV3dSub(&normal, endNormal, startNormal);
                            RwV3dScale(&normal, &normal, scale);
                            RwV3dAdd(&normal, &normal, startNormal);
                           *dataTmp++ = (RwInt8)(normal.x * 127.0f);
                           *dataTmp++ = (RwInt8)(normal.y * 127.0f);
                           *dataTmp++ = (RwInt8)(normal.z * 127.0f);
                           *dataTmp++ = 0;

                            INDEXINC();
                        }
                    }
                }
                else
                {
                    if (pvtdata->clinfo[CL_NORMAL].attrib&CL_ATTRIB_OPAQUE)
                    {
                        while (j--)
                        {
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                        }
                    }
                    else
                    {
                        while (j--)
                        {
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                        }
                    }
                }

                STRIPREVERSE(stripTmp);

                data += ps2ResHeader->fieldRec[CL_NORMAL].skip;
                dataTmp = (RwUInt8 *)data;
            }
        }
    }
#else /* !FASTMORPH */
    ps2ResHeader->morphStart = interpolator->startMorphTarget;
    ps2ResHeader->morphFinish = interpolator->endMorphTarget;
    ps2ResHeader->morphNum = geom->numMorphTargets;

    /* Even if (start == end), we need to instance the second morphtarget.
     * This is because we use the predicate on (geom->numMorphTargets > 1)
     * everywhere else and we need to be consistent (if we didn't upload the
     * second morphtarget, VU1 would use the (uninitialised) values anyway). */
    if (geom->numMorphTargets > 1)
    {
        endMorphTarget = &geom->morphTarget[interpolator->endMorphTarget];

        if (FALSE != instanceXYZ)
        {
            u_long128 *data;
            RwReal *dataTmp;
            RwV3d *vertices = endMorphTarget->verts;

            RWASSERT(NULL != vertices);

            data = ps2ResHeader->data +
                   ps2ResHeader->fieldRec[CL_MAXCL].dataoffset;
#ifdef FASTMORPH
            if (geom->numMorphTargets > 1)
            {
                data = ps2ResHeader->data +
                       ps2ResHeader->fieldRec[CL_MAXCL].morphDataoffset;
            }
#endif /* FASTMORPH */
            dataTmp = (RwReal *)data;

            INDEXRESET();

            vertCounter = numVerts;
            while (vertCounter > 0)
            {
                if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
                {
                    data -= ps2ResHeader->fieldRec[CL_MAXCL].reverse;
                    dataTmp = (RwReal *)data;
                    j = vertCounter;
                    vertCounter = 0;
                }
                else
                {
                    j = ps2ResHeader->fieldRec[CL_MAXCL].morphNumVerts;
                    vertCounter -= (j - stripTmp);
                }

                while(j--)
                {
                    RxVertexIndex idx = INDEXGET();
                    RwV3d        *pos = &(vertices[idx]);

                   *dataTmp++ = pos->x;
                   *dataTmp++ = pos->y;
                   *dataTmp++ = pos->z;

                    INDEXINC();
                }

                STRIPREVERSE(stripTmp);

                data += ps2ResHeader->fieldRec[CL_MAXCL].morphSkip;
                dataTmp = (RwReal *)data;
            }
        }
        if (FALSE != instanceNormal)
        {
            u_long128 *data;
            RwUInt8 *dataTmp;
            RwV3d   *normals = endMorphTarget->normals;

            data = ps2ResHeader->data +
                   ps2ResHeader->fieldRec[CL_MAXCL+1].dataoffset;
#ifdef FASTMORPH
            if (geom->numMorphTargets > 1)
            {
                data = ps2ResHeader->data +
                       ps2ResHeader->fieldRec[CL_MAXCL+1].morphDataoffset;
            }
#endif /* FASTMORPH */
            dataTmp = (RwUInt8 *)data;

            INDEXRESET();

            vertCounter = numVerts;
            while (vertCounter > 0)
            {
                if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
                {
                    data -= ps2ResHeader->fieldRec[CL_MAXCL+1].reverse;
                    dataTmp = (RwUInt8 *)data;
                    j = vertCounter;
                    vertCounter = 0;
                }
                else
                {
                    j = ps2ResHeader->fieldRec[CL_MAXCL+1].morphNumVerts;
                    vertCounter -= (j - stripTmp);
                }

                if (rwObjectTestFlags(geom, rpGEOMETRYNORMALS))
                {
                    if (pvtdata->clinfo[CL_MAXCL+1].attrib&CL_ATTRIB_OPAQUE)
                    {
                        while (j--)
                        {
                            RwUInt32 idx    = INDEXGET();
                            RwV3d   *normal = &(normals[idx]);

                           *dataTmp++ = (RwInt8)(normal->x * 127.0f);
                           *dataTmp++ = (RwInt8)(normal->y * 127.0f);
                           *dataTmp++ = (RwInt8)(normal->z * 127.0f);

                            INDEXINC();
                        }
                    }
                    else
                    {
                        while (j--)
                        {
                            RwUInt32 idx    = INDEXGET();
                            RwV3d   *normal = &(normals[idx]);

                           *dataTmp++ = (RwInt8)(normal->x * 127.0f);
                           *dataTmp++ = (RwInt8)(normal->y * 127.0f);
                           *dataTmp++ = (RwInt8)(normal->z * 127.0f);
                           *dataTmp++ = 0;

                            INDEXINC();
                         }
                    }
                }
                else
                {
                    if (pvtdata->clinfo[CL_MAXCL+1].attrib&CL_ATTRIB_OPAQUE)
                    {
                        while (j--)
                        {
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                        }
                    }
                    else
                    {
                        while (j--)
                        {
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                            *dataTmp++ = 0;
                        }
                    }
                }

                STRIPREVERSE(stripTmp);

                data += ps2ResHeader->fieldRec[CL_MAXCL+1].morphSkip;
                dataTmp = (RwUInt8 *)data;
            }
        }
    }
#endif /* !FASTMORPH */

    /* NOTE: we *should* treat trying to get UVs from an untextured object as
     * an error and assert on it, but realistically people (especially our
     * examples!) will throw textured and untextured objects at the same
     * default pipelines and expect them to work... :o/ */
    if ((  pvtdata->clinfo[CL_UV].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(pvtdata->clinfo[CL_UV].attrib & CL_ATTRIB_DONT_FILL)) &&
        (needToReInstance &
               (rpGEOMETRYLOCKTEXCOORDS|rpGEOMETRYLOCKTEXCOORDS2|rpGEOMETRYLOCKPOLYGONS)) &&
        rwObjectTestFlags(geom, rpGEOMETRYTEXTURED|rpGEOMETRYTEXTURED2) )
    {
        u_long128 *data;
        RwReal *dataTmp;
        RwTexCoords *tc = geom->texCoords[0];

        /* RWASSERT(rwObjectTestFlags(geom, rpGEOMETRYTEXTURED ) ||
                 rwObjectTestFlags(geom, rpGEOMETRYTEXTURED2)   ); */

        data = ps2ResHeader->data +
               ps2ResHeader->fieldRec[CL_UV].dataoffset;
#ifdef FASTMORPH
        if (geom->numMorphTargets > 1)
        {
            data = ps2ResHeader->data +
                   ps2ResHeader->fieldRec[CL_UV].morphDataoffset;
        }
#endif /* FASTMORPH */
        dataTmp = (RwReal *)data;

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
            {
                data -= ps2ResHeader->fieldRec[CL_UV].reverse;
                dataTmp = (RwReal *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2ResHeader->fieldRec[CL_UV].numVerts;
#ifdef FASTMORPH
                if (geom->numMorphTargets > 1)
                {
                    j = ps2ResHeader->fieldRec[CL_UV].morphNumVerts;
                }
#endif /* FASTMORPH */
                vertCounter -= (j - stripTmp);
            }

            while (j--)
            {
                RxVertexIndex idx   = INDEXGET();
                RwTexCoords  *coord = &(tc[idx]);

               *dataTmp++ = coord->u;
               *dataTmp++ = coord->v;

                INDEXINC();
            }

            STRIPREVERSE(stripTmp);

            j = ps2ResHeader->fieldRec[CL_UV].skip;
#ifdef FASTMORPH
            if (geom->numMorphTargets > 1)
            {
                j = ps2ResHeader->fieldRec[CL_UV].morphSkip;
            }
#endif /* FASTMORPH */
            data += j;
            dataTmp = (RwReal *)data;
        }
    }

    if ((  pvtdata->clinfo[CL_UV2].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(pvtdata->clinfo[CL_UV2].attrib & CL_ATTRIB_DONT_FILL)) &&
        (needToReInstance &
               (rpGEOMETRYLOCKTEXCOORDS|rpGEOMETRYLOCKTEXCOORDS2|
                rpGEOMETRYLOCKPOLYGONS)) &&
        rwObjectTestFlags(geom, rpGEOMETRYTEXTURED2) )
    {
        u_long128 *data;
        RwReal *dataTmp;
        RwTexCoords *tc1 = geom->texCoords[0];
        RwTexCoords *tc2 = geom->texCoords[1];

        /* RWASSERT(rwObjectTestFlags(geom, rpGEOMETRYTEXTURED2)); */

        data = ps2ResHeader->data +
               ps2ResHeader->fieldRec[CL_UV2].dataoffset;
#ifdef FASTMORPH
        if (geom->numMorphTargets > 1)
        {
            data = ps2ResHeader->data +
                   ps2ResHeader->fieldRec[CL_UV2].morphDataoffset;
        }
#endif /* FASTMORPH */
        dataTmp = (RwReal *)data;

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
            {
                data -= ps2ResHeader->fieldRec[CL_UV2].reverse;
                dataTmp = (RwReal *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2ResHeader->fieldRec[CL_UV2].numVerts;
#ifdef FASTMORPH
                if (geom->numMorphTargets > 1)
                {
                    j = ps2ResHeader->fieldRec[CL_UV2].morphNumVerts;
                }
#endif /* FASTMORPH */
                vertCounter -= (j - stripTmp);
            }

            while (j--)
            {
                RxVertexIndex idx   = INDEXGET();
                RwTexCoords  *coord;

                /* Update both UV sets for now */
                coord = &(tc1[idx]);
                *dataTmp++ = coord->u;
                *dataTmp++ = coord->v;

                coord = &(tc2[idx]);
                *dataTmp++ = coord->u;
                *dataTmp++ = coord->v;

                INDEXINC();
            }

            STRIPREVERSE(stripTmp);

            j = ps2ResHeader->fieldRec[CL_UV2].skip;
#ifdef FASTMORPH
            if (geom->numMorphTargets > 1)
            {
                j = ps2ResHeader->fieldRec[CL_UV2].morphSkip;
            }
#endif /* FASTMORPH */
            data += j;
            dataTmp = (RwReal *)data;
        }
    }

    if ((  pvtdata->clinfo[CL_RGBA].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(pvtdata->clinfo[CL_RGBA].attrib & CL_ATTRIB_DONT_FILL)) &&
        (needToReInstance & (rpGEOMETRYLOCKPRELIGHT|rpGEOMETRYLOCKPOLYGONS)))
    {
        u_long128 *data;
        RwUInt32 *dataTmp;
        RwRGBA *preLitLum = geom->preLitLum;

        data = ps2ResHeader->data +
               ps2ResHeader->fieldRec[CL_RGBA].dataoffset;
#ifdef FASTMORPH
        if (geom->numMorphTargets > 1)
        {
            data = ps2ResHeader->data +
                   ps2ResHeader->fieldRec[CL_RGBA].morphDataoffset;
        }
#endif /* FASTMORPH */
        dataTmp = (RwUInt32 *)data;

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2ResHeader->batchSize)
            {
                data -= ps2ResHeader->fieldRec[CL_RGBA].reverse;
                dataTmp = (RwUInt32 *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2ResHeader->fieldRec[CL_RGBA].numVerts;
#ifdef FASTMORPH
                if (geom->numMorphTargets > 1)
                {
                    j = ps2ResHeader->fieldRec[CL_RGBA].morphNumVerts;
                }
#endif /* FASTMORPH */
                vertCounter -= (j - stripTmp);
            }

            if (rwObjectTestFlags(geom, rpGEOMETRYPRELIT))
            {
                while (j--)
                {
#if (!defined(OVERRIDELIGHT))
                    RxVertexIndex idx = INDEXGET();
                    const RwRGBA *col = &(preLitLum[idx]);

                   *dataTmp++ = ((RwUInt32)((RwUInt8)col->red  ) <<  0) |
                                ((RwUInt32)((RwUInt8)col->green) <<  8) |
                                ((RwUInt32)((RwUInt8)col->blue ) << 16) |
                                ((RwUInt32)((RwUInt8)col->alpha) << 24);
                    INDEXINC();
#else /* !OVERRIDELIGHT */
                    *dataTmp++ = 0xffffffff;
#endif /* !OVERRIDELIGHT */
                }
            }
            else
            {
                while (j--)
                {
#if (!defined(SUBSISTLIGHT))
                    *dataTmp++ = 0xff000000;
#else  /* !SUBSISTLIGHT */
                    *dataTmp++ = 0xffffffff;
#endif  /* !SUBSISTLIGHT */
                }
            }

            STRIPREVERSE(stripTmp);

            j = ps2ResHeader->fieldRec[CL_RGBA].skip;
#ifdef FASTMORPH
            if (geom->numMorphTargets > 1)
            {
                j = ps2ResHeader->fieldRec[CL_RGBA].morphSkip;
            }
#endif /* !FASTMORPH */
            data += j;
            dataTmp = (RwUInt32 *)data;
        }
    }

#ifdef REDEBUG
#ifdef DMADUMPDATA
    /* dump the entire structure to stdout */
    {
        int j;
        int ze = 0;
        volatile int *vol = &ze;
        u_long128* ptr;

        /* Now dump the data */
        ptr = ps2ResHeader->data;
        for (;(int)ptr < (int)((char*)ps2ResHeader +
                         ((RwResEntry *)ps2ResHeader - 1)->size); ptr++)
        {
            printf("%x: %.16lx %.16lx\n", (int)ptr, *((long*)ptr+1), *(long*)ptr);
            /* We put a pause in here as dsnetm has a buffer overrun problem */
            for (j=0; j<100000; j++)
            {
                if (*vol)
                {
                    printf("Odd?\n");
                }
            }

        }
    }
#endif /* DMADUMPDATA */
#endif /* REDEBUG */

    RWRETURN(TRUE);
}


static void
envisionClusters(rwPS2MatInstancePvtData *pvtData, rwPS2ResEntryHeader *ps2ResHeader)
{
    RWFUNCTION(RWSTRING("envisionClusters"));

    envisionclustersMacro(pvtData, ps2ResHeader);

    RWRETURNVOID();
}

/****************************************************************************
 _rwRabinsMatInstanceCode
 */
RwBool /* success? */
_rwRabinsMatInstanceCode(RxPS2DMASessionRecord * const DMASessionRecord,
                         const RxPS2Mesh * const Mesh,
                         rwPS2MatInstancePvtData *pvtdata)
{
    RwResEntry **repEntry = (RwResEntry **)NULL;
    const RpMeshHeader *meshHeader;
    rwPS2ResEntryHeader *ps2ResHeader = (rwPS2ResEntryHeader *)NULL;
    RwUInt32 numBatches = 0;
    RwUInt32 batchSize = 0 ;
    RwUInt32 batchesPerTag = 0;
    RwUInt32 effectiveTotalVerts = 0;
    RwUInt32 size = 0;
    RwUInt32 numVerts;
    RwUInt16 overloadFlags; /* Aren't these dead yet? */
    RwUInt16 needToReInstance = 0;
#ifndef FASTMORPH
    rwPS2FieldRec fieldRec[CL_MAXCL];
#else /* !FASTMORPH */
    rwPS2FieldRec fieldRec[CL_MAXCL+2];
#endif /* !FASTMORPH */
    const RpMesh *mesh = Mesh->mesh;
    int i, j;
#ifdef REDEBUG
    u_long128 *finaladdr;
#endif /* REDEBUG */
#ifdef FASTMORPH
    RwInt32 numMorphTargets = 1;
    RwUInt32 fastMorphing = 0;
#endif /* FASTMORPH */

    RWFUNCTION(RWSTRING("_rwRabinsMatInstanceCode"));

    /* We have the ResEntry and size calculation here in common for both */
    /* atomic and worldsector sourced meshes. However, the actual data */
    /* handling code is separated in functions by source object */

    /* Some how we get a ResEntry** here... */
    /* If there isn't a pointer to a valid ResEntry, return a pointer to a
       free slot containing a NULL. */

    repEntry = Mesh->cacheEntryRef;
#ifdef REDEBUG
    printf("Entered _rwRabinsMatInstanceCode. *(Mesh->cacheEntryRef) = %p\n",
           *repEntry);
#endif /* REDEBUG */

    RWASSERT((DMASessionRecord->objType == rxOBJTYPE_WORLDSECTOR) ||
             (DMASessionRecord->objType == rxOBJTYPE_ATOMIC) ||
             (DMASessionRecord->objType == rxOBJTYPE_IM3D));

    /* We extract all the data we need from the parent object to test */
    /* if reinstancing is required */

    if (DMASessionRecord->objType == rxOBJTYPE_WORLDSECTOR)
    {
#ifdef REDEBUG
        printf("parent is a world sector\n");
#endif /* REDEBUG */
        meshHeader = DMASessionRecord->sourceObject.worldSector->mesh;
        overloadFlags = rwObjectGetFlags((RpWorld*)RWSRCGLOBAL(curWorld))
            & WORLDRENDERTYPEMASK;
    }
    else if (DMASessionRecord->objType == rxOBJTYPE_ATOMIC)
    {
        RpGeometry *geom
            = RpAtomicGetGeometry(DMASessionRecord->sourceObject.atomic);
        RpInterpolator *interpolator
            = &(DMASessionRecord->sourceObject.atomic->interpolator);

#ifdef REDEBUG
        printf("parent is an atomic\n");
#endif /* REDEBUG */
        meshHeader = geom->mesh;
        overloadFlags = rwObjectGetFlags(geom) & ATOMICRENDERTYPEMASK;
        needToReInstance = geom->lockedSinceLastInst;
#ifdef FASTMORPH
        numMorphTargets = geom->numMorphTargets;
        if (numMorphTargets > 1) fastMorphing = 2;
#endif /* FASTMORPH */

#ifdef REDEBUG
        printf("needToReInstance %x\n", (RwUInt32)needToReInstance);
#endif /* REDEBUG */

        /* SHOULD check to see if numMorphtargets has changed from 1 to (> 1)
         * or vice versa... but this SHOULD be a locking issue so I'll leave
         * it as is and we'll fix it properly if it occurs in an app... */
        if ((fastMorphing) &&
            (interpolator->flags & (RwInt32)rpINTERPOLATORDIRTYINSTANCE))
        {
#ifndef FASTMORPH
            /* needToReInstance set here so that the resEntry gets thrown away
             * below... POLYGONS not locked so we can cache resEntry stuff */
            needToReInstance |= (rpGEOMETRYLOCKVERTICES |
                                 rpGEOMETRYLOCKNORMALS);

            /* Flag reset once per atomic, not per mesh.
             * (otherwise only the first mesh is animated!) */
#ifdef REDEBUG
            printf("interpolator forced rebuild (%x)\n",
                   (RwUInt32)needToReInstance);
#endif /* REDEBUG */
#else /* !FASTMORPH */
            /* Rules are a bit different here. We check to see if the morph
             * start/end have changed, and only then reinstance */
            if ( (*repEntry) &&
                 ( ( ((rwPS2ResEntryHeader *)(*repEntry + 1))->morphStart
                     != interpolator->startMorphTarget ) ||
                   ( ((rwPS2ResEntryHeader *)(*repEntry + 1))->morphFinish
                     != interpolator->endMorphTarget )   ||
                   ( ((rwPS2ResEntryHeader *)(*repEntry + 1))->morphNum
                     != geom->numMorphTargets ) ) )
            {
                /* Disconnect resource entry to be garbage-collected
                 * once DMA is done with it */
                (*repEntry)->ownerRef = (RwResEntry **) NULL;
                *repEntry = (RwResEntry *) NULL;

#ifdef REDEBUG
                printf("interpolator forced disconnect of resEntry\n");
                printf ("%d %d %d\n", interpolator->startMorphTarget,
                                      interpolator->endMorphTarget,
                                      geom->numMorphTargets);
#endif /* REDEBUG */
            }
#endif /* !FASTMORPH */
        }
    }
    else /* DMASessionRecord->objType == rxOBJTYPE_IM3D */
    {
        meshHeader = ((const RpMeshHeader *)mesh) - 1;
        overloadFlags = 0; /* I thought overloading was dead? */
        /* The stash flags will be tested for rwIM3D_VERTEXUV
         * inside _reInstanceFromIm3D() */
        needToReInstance = rpGEOMETRYLOCKALL;
    }

    if ((pvtdata->pipeType    & meshHeader->flags) !=
        (rpMESHHEADERPRIMMASK & meshHeader->flags))
    {
        RwDebugSendMessage(
            rwDEBUGMESSAGE,
            "_rwRabinsMatInstanceCode",
            "Incorrect primitive type for the current pipeline: use RxPipelineNodePS2MatInstanceSetVUBufferSizes for constructing trilist/tristrip/trifan/linelist/polyline pipelines and RxPipelineNodePS2MatInstanceSetVUPointListBufferSize for point list objects");
        RWRETURN(FALSE);
    }

    /* Check validity of unindexed meshes */
    RWASSERT( ((NULL == mesh->indices)    &&
               (  meshHeader->flags & rpMESHHEADERUNINDEXED )) ||
              ((NULL != mesh->indices)    &&
               (!(meshHeader->flags & rpMESHHEADERUNINDEXED)))   );

    /* For point-lists, we set up a triStrip transform and GSPrim in
     * PS2ObjAllInOne, but a user instance callback/node can override
     * this by editing DMASessionRecord->transType and/or uploading
     * their own GIF tag(s) */

    numVerts = mesh->numIndices;

    /* We expand trifans and polylines during instancing into
     * trilists and linelists respectively and numverts is meant to
     * be numverts in the DMA chain and on VU1 so munge the values.
     * Note that this affects the number of batches but not batchsize
     * and fieldRec.reverse but not fieldRec.skip */
    if (meshHeader->flags & rpMESHHEADERTRIFAN)
    {
        numVerts = (numVerts - 2)*3;
    }
    else if (meshHeader->flags & rpMESHHEADERPOLYLINE)
    {
        numVerts = (numVerts - 1)*2;
    }

#ifdef REDEBUG
    printf("numVerts %d\n", numVerts);
#endif /* REDEBUG */

    /* Does it exist? If so will it need its data updated? */
    if (*repEntry)
    {
        ps2ResHeader = (rwPS2ResEntryHeader*)(*repEntry+1);

        /* stash serial number */
        DMASessionRecord->serialNum = meshHeader->serialNum;
#ifdef REDEBUG
        printf("We have a ResEntry\n");
#endif /* REDEBUG */
        if (((ps2ResHeader->serialNum & 0xFFFF) != meshHeader->serialNum) ||
            (ps2ResHeader->overloadFlags != overloadFlags) ||
            (needToReInstance & rpGEOMETRYLOCKPOLYGONS))
        {
            /* Release resources to force re-instance */

#ifdef REDEBUG
            printf("... but it is out of date\n");
#endif /* REDEBUG */
            /* We should really do this as a last resort. eg:
             * if no refs and count is the same, just reinstance into entry
             * if refs, can we find another entry that is "free" but extant
             * if none are free, if we have a buffer slot, alloc afresh
             * else spin until one of the buffers becomes free */

            if ((ps2ResHeader->numVerts == numVerts) &&
                (ps2ResHeader->meshType == meshHeader->flags) &&
#ifdef FASTMORPH
                (ps2ResHeader->morphNum == numMorphTargets) &&
#endif /* FASTMORPH */
                !(needToReInstance & rpGEOMETRYLOCKPOLYGONS))
            {
                /* Cache sizes and offset to save recalculation */
                size = (*repEntry)->size;
                /* How nasty */
#ifndef FASTMORPH
                for (i=0; i<((int)(CL_MAXCL)); i++)
#else /* !FASTMORPH */
                for (i=0; i<((int)(CL_MAXCL + fastMorphing)); i++)
#endif /* !FASTMORPH */
                {
                    fieldRec[i] = ps2ResHeader->fieldRec[i];
                }
                batchSize = ps2ResHeader->batchSize;
                batchesPerTag = ps2ResHeader->batchesPerTag;
                numBatches = ps2ResHeader->numBatches;
#ifdef REDEBUG
                printf("We cache the old sizes etc:\n");
                printf(" size %d\n", size);
                printf(" batchSize %d\n", batchSize);
                printf(" batchesPerTag %d\n", batchesPerTag);
                printf(" numBatches %d\n", numBatches);
                {
                    int ze = 0;
                    volatile int *vol = &ze;

#ifndef FASTMORPH
                    for (i = 0; i< ((int)(CL_MAXCL)); i++)
#else /* !FASTMORPH */
                    for (i = 0; i< ((int)(CL_MAXCL + 2)); i++)
#endif /* !FASTMORPH */
                    {
                        printf(" fieldRec %d\n", i);
                        printf("  numVerts %d\n", fieldRec[i].numVerts);
#ifdef FASTMORPH
                        printf("  morphNumVerts %d\n", fieldRec[i].morphNumVerts);
#endif /* FASTMORPH */
                        printf("  dataoffset %d\n", fieldRec[i].dataoffset);
#ifdef FASTMORPH
                        printf("  morphDataoffset %d\n", fieldRec[i].morphDataoffset);
#endif /* FASTMORPH */
                        printf("  skip %d\n", fieldRec[i].skip);
                        printf("  reverse %d\n", fieldRec[i].reverse);
                        printf("  vuoffset %d\n", fieldRec[i].vuoffset);
                        /* We put a pause in here as dsnetm has a buffer overrun problem */
                        for (j=0; j<100000; j++)
                        {
                            if (*vol)
                            {
                                printf("Odd?\n");
                            }
                        }
                    }
                }
#endif /* REDEBUG */
            }
            else
            {
#ifdef REDEBUG
                printf("We can't cache\n");
#endif /* REDEBUG */
                /* Set size to zero to indicate recalculation required */


                /* NOTE: Following is here temporarily. Once partial reinstancing
                 * (create new DMA chain, cache size/layout info from the old
                 * resHeader) has been fixed (it crashes DMA atm), this will be
                 * moved outside this if/else again. For now, partial reinstancing
                 * is done in-place in the existing DMA chain. */

                /* Disconnect resource entry to be garbage-collected
                 * once DMA is done with it */
                (*repEntry)->ownerRef = (RwResEntry **)NULL;
                *repEntry = (RwResEntry *)NULL;
            }
            /* Should disconnect resEntry here - see NOTE above. */
            /* This use of needToReInstance is temporary too */
            if (((ps2ResHeader->serialNum & 0xFFFF) != meshHeader->serialNum) ||
                (ps2ResHeader->overloadFlags != overloadFlags) )
            {
                /* Unpredictable w.r.t what clusters changed...
                 * certainly in the case of overloadFlags changing, but serialNum?? */
                needToReInstance = rpGEOMETRYLOCKALL;
            }
        }
    }
    else
    {
        /* Force the recalculation of the resource size and offsets */

        /* This use of needToReInstance is temporary too */
        needToReInstance = rpGEOMETRYLOCKALL;
    }

    /* Early out */
    if (*repEntry)
    {
        if (!needToReInstance)
        {
            /* At this point we can now actually copy in the object data
             * The macro avoids predication/function-call */
            if (pvtdata->numBreakOuts > 0)
            {
                envisionclustersMacro(pvtdata, ps2ResHeader);
            }
#ifdef REDEBUG
            printf("Don't need to do anything, exiting\n\n");
#endif /* REDEBUG */
            RwResourcesUseResEntry(*repEntry);
            RWRETURN(TRUE);
        }
        else
        {
            /* NOTE: this else is temporary, as is the use of
             * needToReInstance to flag reinstancing requirements on a
             * per-cluster basis. This will be removed (again, as per
             * other NOTEs above) once partial reinstancing has been
             * fixed. */

            /* This path should never be used for worldsectors or im3d.
             * It is for when you've modified vertex values but not topology.
             * It causes DMA data to be edited in place and causes the
             * 'into-the-future' effect but saves a full reinstance. */
            /* RWASSERT(DMASessionRecord->objType == rxOBJTYPE_ATOMIC); */
            /* Sadly, it turns out to be not as simple as that :o/
             * The usage of reinstance hints is very messy and unclear in
             * RW... changing world flags will change the serial number and
             * get us here... and stuff... so I'll provide a worldsector path */

            /* At this point we can now actually copy in the object data */
            if (pvtdata->numBreakOuts > 0)
            {
                envisionClusters(pvtdata, ps2ResHeader);
            }
            RwResourcesUseResEntry(*repEntry);
            /* For now, force reinstance all */
            /* needToReInstance = rpGEOMETRYLOCKALL; */
            if(DMASessionRecord->objType == rxOBJTYPE_ATOMIC)
            {
#ifdef REDEBUG
                printf("Exiting via reInstanceFromAtomic\n\n");
#endif /* REDEBUG */
                RWRETURN(reInstanceFromAtomic(pvtdata, 
                                              ps2ResHeader, 
                                              mesh,
                                              (RpMeshHeaderFlags)meshHeader->flags,
                                              numVerts, 
                                              needToReInstance, 
                                              DMASessionRecord));
            }
            else
            {
                /* Ok, it should definitely NOT happen for Im3D! :o) */
                RWASSERT(DMASessionRecord->objType == rxOBJTYPE_WORLDSECTOR);
                RWRETURN(reInstanceFromWorldSector(pvtdata, 
                                              ps2ResHeader, 
                                              mesh,
                                              (RpMeshHeaderFlags)meshHeader->flags,
                                              numVerts, 
                                              DMASessionRecord));
            }

        }
    }

    if (!size)
    {
        /* NOTE: while bug-fixing partial reinstancing, we are turning
         * it off. If we reinstance, atm it's either fully or in-place. */
        RWASSERT(NULL == *repEntry);

#ifdef REDEBUG
        printf("Figuring sizes etc\n");
#endif /* REDEBUG */
        /* We have to figure out how big the required area is and what the */
        /* offset of each of the data area is */

        /* First, what is the effective total vertex count? */
        if (meshHeader->flags & rpMESHHEADERTRISTRIP)
        {
#ifdef REDEBUG
            printf("We have a tristrip\n");
#endif /* REDEBUG */
            batchSize = pvtdata->triStrip.batchSize;
            batchesPerTag = pvtdata->triStrip.batchesPerTag;
#ifdef FASTMORPH
            if (fastMorphing)
            {
                batchSize = pvtdata->triStrip.morphBatchSize;
                batchesPerTag = pvtdata->triStrip.morphBatchesPerTag;
            }
#endif /* FASTMORPH */
            numBatches = ((numVerts - 2) + ((batchSize - 2) - 1)) /
                         (batchSize - 2);
            effectiveTotalVerts = numVerts + 2*(numBatches - 1);
            /* We set up the initial fieldRec elements */
#ifndef FASTMORPH
            for (i=0; i<((int)(CL_MAXCL)); i++)
#else /* !FASTMORPH */
            for (i=0; i<((int)(CL_MAXCL + fastMorphing)); i++)
#endif /* !FASTMORPH */
            {
                fieldRec[i] = pvtdata->triStrip.fieldRec[i];
            }
        }
        else
        {
#ifdef REDEBUG
            printf("We have a trilist\n");
#endif /* REDEBUG */
            batchSize = pvtdata->triList.batchSize;
            batchesPerTag = pvtdata->triList.batchesPerTag;
#ifdef FASTMORPH
            if (fastMorphing)
            {
                batchSize = pvtdata->triList.morphBatchSize;
                batchesPerTag = pvtdata->triList.morphBatchesPerTag;
            }
#endif /* FASTMORPH */
            numBatches = (numVerts + (batchSize - 1))/batchSize;
            effectiveTotalVerts = numVerts;
#ifndef FASTMORPH
            for (i=0; i<((int)(CL_MAXCL)); i++)
#else /* !FASTMORPH */
            for (i=0; i<((int)(CL_MAXCL + fastMorphing)); i++)
#endif /* !FASTMORPH */
            {
                fieldRec[i] = pvtdata->triList.fieldRec[i];
            }
        /* Batch size is apparently number of vertices (of the current size)
         * so no change is needed to get the correct number for linelists
         * (batch size is guaranteed to be a multiple of 12 (of which 2 is
         * a factor), so don't worry about indices from the same line being
         * split into different batches!) */
        }
#ifdef REDEBUG
        printf("batchSize %d\n", batchSize);
        printf("batchesPerTag %d\n", batchesPerTag);
        printf("numBatches %d\n", numBatches);
        printf("effectiveTotalVerts %d\n", effectiveTotalVerts);
        {
            int ze = 0;
            volatile int *vol = &ze;

#ifndef FASTMORPH
            for (i = 0; i < ((int)(CL_MAXCL)); i++)
#else /* !FASTMORPH */
            for (i = 0; i < ((int)(CL_MAXCL + 2)); i++)
#endif /* !FASTMORPH */
            {
                printf(" fieldRec %d\n", i);
                printf("  numVerts %d\n", fieldRec[i].numVerts);
#ifdef FASTMORPH
                printf("  morphNumVerts %d\n", fieldRec[i].morphNumVerts);
#endif /* FASTMORPH */
                printf("  dataoffset %d\n", fieldRec[i].dataoffset);
#ifdef FASTMORPH
                printf("  morphDataoffset %d\n", fieldRec[i].morphDataoffset);
#endif /* FASTMORPH */
                printf("  skip %d\n", fieldRec[i].skip);
#ifdef FASTMORPH
                printf("  morphSkip %d\n", fieldRec[i].morphSkip);
#endif /* FASTMORPH */
                printf("  vuoffset %d\n", fieldRec[i].vuoffset);
                /* We put a pause in here as dsnetm has a buffer overrun problem */
                for (j=0; j<100000; j++)
                {
                    if (*vol)
                    {
                        printf("Odd?\n");
                    }
                }
            }
        }
#endif /* REDEBUG */

        if (pvtdata->totallyOpaque)
        {
            /* Only the reverse field needs be updated */
            int viftags = 1;
            int lastReverse = 0;
            int vertsOver = ((batchSize*numBatches) - effectiveTotalVerts);

            size = 0;
#ifdef REDEBUG
            printf("Totally opaque\n");
#endif /* REDEBUG */
            /* Skip's value is being used as a flag here for whether
             * this cluster requires space CPU-side (same as testing
             * (pvtdata->clinfo[i].attrib & CL_ATTRIB_REQUIRED, *given*
             * that we know this cluster is opaque, which all are here) */
            size += fieldRec[CL_XYZ].skip?
                (viftags++,((3*effectiveTotalVerts + 3) >> 2)):0;
            fieldRec[CL_XYZ].reverse = lastReverse;
            lastReverse += fieldRec[CL_XYZ].skip?(vertsOver*3) >> 2:0;

            size += fieldRec[CL_UV].skip?
                (viftags++,((2*effectiveTotalVerts + 3) >> 2)):0;
            fieldRec[CL_UV].reverse = lastReverse;
            lastReverse += fieldRec[CL_UV].skip?vertsOver >> 1:0;

            size += fieldRec[CL_UV2].skip?
                (viftags++,((4*effectiveTotalVerts + 3) >> 2)):0;
            fieldRec[CL_UV2].reverse = lastReverse;
            lastReverse += fieldRec[CL_UV2].skip?vertsOver:0;

            size += fieldRec[CL_RGBA].skip?
                (viftags++,((effectiveTotalVerts + 3) >> 2)):0;
            fieldRec[CL_RGBA].reverse = lastReverse;
            lastReverse += fieldRec[CL_RGBA].skip?vertsOver >> 2:0;

            size += fieldRec[CL_NORMAL].skip?
                (viftags++,(((batchSize*3 + 15) >> 4)*(numBatches - 1)
                       + (((effectiveTotalVerts - (batchSize*(numBatches - 1)))
                            *3 + 15) >> 4))):0;
            fieldRec[CL_NORMAL].reverse = lastReverse;

#ifdef FASTMORPH
            if (fastMorphing)
            {
                lastReverse +=
                    fieldRec[CL_NORMAL].skip ?
                    (((batchSize*3 + 15) >> 4) - (((effectiveTotalVerts -
                            (batchSize*(numBatches - 1)))*3 + 15) >> 4))
                                             : 0;
                size += (viftags++,((3*effectiveTotalVerts + 3) >> 2));
                fieldRec[CL_MAXCL].reverse = lastReverse;
                lastReverse += (vertsOver*3) >> 2;

                size += (viftags++,(((batchSize*3 + 15) >> 4)*(numBatches - 1)
                       + (((effectiveTotalVerts - (batchSize*(numBatches - 1)))
                            *3 + 15) >> 4)));
                fieldRec[CL_MAXCL+1].reverse = lastReverse;
            }
#endif /* FASTMORPH */

            /* Vif commands */
            size += viftags*numBatches;
            /* Dma tags */
            size += (numBatches + batchesPerTag - 1) / batchesPerTag;
#ifdef REDEBUG
            printf("size = %d\n", size);
#endif /* REDEBUG */
#ifdef DMAALIGN
            /* Note: This isn't used for more that the first dma tag yet! */

            /* We know that we can round up the qwc in the dma tag so that */
            /* the next batch will be aligned as the max qwc count +1 is   */
            /* 8 qw aligned */
            size += ((numBatches + batchesPerTag - 1) / batchesPerTag)*8;
            /* We will pad the beginning with a Dma tag and nops if required */
#ifdef REDEBUG
            printf("but DMAALIGN was defined so size = %d\n", size);
#endif /* REDEBUG */
#endif /* DMAALIGN */
        }
        else
        {
            int nonOpaqueOffset;
            int lastReverse = 0;
            int vertsEndCnt = (effectiveTotalVerts - (batchSize*(numBatches - 1)));
            short skip;
            int offset;

#ifdef REDEBUG
            printf("Some striped data\n");
#endif /* REDEBUG */
            /* I feel that some more of this code could be moved up into the
             * construction time function. E.g qw batch size could be computed,
             * though we would then be switching on strip/non-strip here */

            /* Where it would be if there was no opaque data */
            nonOpaqueOffset = 2*(pvtdata->numStripes + 1)*numBatches;
            size = 0;
            for (i = 0; i < ((int)(CL_MAXCL)); i++)
            {
                if (pvtdata->clinfo[i].attrib & CL_ATTRIB_REQUIRED)
                {
                    if (pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE)
                    {
                        int qwBatchSize;
                        /* Per batch size is complicated by the fact the */
                        /* each batch is not a whole multiple of qw */
#ifdef REDEBUG
                        printf("field %d is required and opaque\n", i);
#endif /* REDEBUG */

                        /* Warning: Non Opaque Offset code overestimates due */
                        /* to not taking into account that all inline data */
                        /* is smaller in the last batch, so the "skip" value */
                        /* is smaller. The lastReverse is used to adjust this */
                        switch (i)
                        {
                        case CL_XYZ:
                            qwBatchSize = (3*batchSize + 3) >> 2;
                            skip = fieldRec[CL_XYZ].skip;
                            offset = fieldRec[CL_XYZ].dataoffset;
#ifdef FASTMORPH
                            if (fastMorphing)
                            {
                                skip = fieldRec[CL_XYZ].morphSkip;
                                offset = fieldRec[CL_XYZ].morphDataoffset;
                            }
#endif /* !FASTMORPH */
                            nonOpaqueOffset = offset +
                                              skip*(numBatches - 1) +
                                              qwBatchSize + 1;
                            fieldRec[CL_XYZ].reverse = lastReverse;
                            lastReverse +=
                                qwBatchSize - ((3*vertsEndCnt + 3) >> 2);
                            break;
                        case CL_UV:
                            qwBatchSize = (batchSize + 1) >> 1;
                            skip = fieldRec[CL_UV].skip;
                            offset = fieldRec[CL_UV].dataoffset;
#ifdef FASTMORPH
                            if (fastMorphing)
                            {
                                skip = fieldRec[CL_UV].morphSkip;
                                offset = fieldRec[CL_UV].morphDataoffset;
                            }
#endif /* !FASTMORPH */
                            nonOpaqueOffset = offset +
                                              skip*(numBatches - 1) +
                                              qwBatchSize + 1;
                            fieldRec[CL_UV].reverse = lastReverse;
                            lastReverse +=
                                qwBatchSize - ((vertsEndCnt + 1) >> 1);
                            break;
                        case CL_UV2:
                            qwBatchSize = (4*batchSize + 3) >> 2;
                            skip = fieldRec[CL_UV2].skip;
                            offset = fieldRec[CL_UV2].dataoffset;
#ifdef FASTMORPH
                            if (fastMorphing)
                            {
                                skip = fieldRec[CL_UV2].morphSkip;
                                offset = fieldRec[CL_UV2].morphDataoffset;
                            }
#endif /* !FASTMORPH */
                            nonOpaqueOffset = offset +
                                              skip*(numBatches - 1) +
                                              qwBatchSize + 1;
                            fieldRec[CL_UV2].reverse = lastReverse;
                            lastReverse +=
                                qwBatchSize - ((4*vertsEndCnt + 3) >> 2);
                            break;
                        case CL_RGBA:
                            qwBatchSize = (batchSize + 3)>>2;
                            skip = fieldRec[CL_RGBA].skip;
                            offset = fieldRec[CL_RGBA].dataoffset;
#ifdef FASTMORPH
                            if (fastMorphing)
                            {
                                skip = fieldRec[CL_RGBA].morphSkip;
                                offset = fieldRec[CL_RGBA].morphDataoffset;
                            }
#endif /* !FASTMORPH */
                            nonOpaqueOffset = offset +
                                              skip*(numBatches - 1) +
                                              qwBatchSize + 1;
                            fieldRec[CL_RGBA].reverse = lastReverse;
                            lastReverse += qwBatchSize - ((vertsEndCnt + 3) >> 2);
                            break;
                        case CL_NORMAL:
                            qwBatchSize = (3*batchSize + 15) >> 4;
                            skip = fieldRec[CL_NORMAL].skip;
                            offset = fieldRec[CL_NORMAL].dataoffset;
#ifdef FASTMORPH
                            if (fastMorphing)
                            {
                                skip = fieldRec[CL_NORMAL].morphSkip;
                                offset = fieldRec[CL_NORMAL].morphDataoffset;
                            }
#endif /* !FASTMORPH */
                            nonOpaqueOffset = offset +
                                              skip*(numBatches - 1) +
                                              qwBatchSize + 1;
                            fieldRec[CL_NORMAL].reverse = lastReverse;
                            lastReverse += qwBatchSize
                                - ((3*vertsEndCnt + 15) >> 4);
                            break;
                        default:
                            RWASSERT(i < 4);
                            break;
                        }
#ifdef REDEBUG
                        printf("nonOpaqueOffset currently at %d\n", nonOpaqueOffset);
                        printf("lastReverse currently at %d\n", lastReverse);
#endif /* REDEBUG */
                    }
                    else
                    {
#ifdef REDEBUG
                        printf("field %d is required and striped\n", i);
                        printf("size was %d\n", size);
#endif /* REDEBUG */

#ifdef REDEBUG
                        printf("__size %d\n", pvtdata->clinfo[i].stride);
#endif /* REDEBUG */
                        /* for striped data we only have each vertex once */
                        size += ((numVerts*
                                  (pvtdata->clinfo[i].stride) + 3) >> 2);
#ifdef REDEBUG
                        printf("size now %d\n", size);
#endif /* REDEBUG */
                        /* We update the numVerts field for non-opaque clusters
                         * in order to affect how instancing occurs (we can
                         * instance all the verts in one go because there is no
                         * in-place tristrip vertex duplication - it is done
                         * by frigging DMA ref tags). */
                        fieldRec[i].numVerts = numVerts;
#ifdef FASTMORPH
                        /* The duplicate XYZ/NORMAL clusters are always opaque
                         * currently but that might change so this is a bit of
                         * future-proofing */
                        fieldRec[i].morphNumVerts = numVerts;
#endif /* FASTMORPH */
                        fieldRec[i].reverse = 0;
                    }
                }
            }
#ifdef FASTMORPH
            if (fastMorphing)
            {
                int qwBatchSize;

                qwBatchSize = (3*batchSize + 3) >> 2;
                skip = fieldRec[CL_MAXCL].morphSkip;
                nonOpaqueOffset = fieldRec[CL_MAXCL].morphDataoffset +
                                  skip*(numBatches - 1) +
                                  qwBatchSize + 1;
                fieldRec[CL_MAXCL].reverse = lastReverse;
                lastReverse += qwBatchSize
                    - ((3*vertsEndCnt + 3) >> 2);

                qwBatchSize = (3*batchSize + 15) >> 4;
                skip = fieldRec[CL_MAXCL+1].morphSkip;
                nonOpaqueOffset = fieldRec[CL_MAXCL+1].morphDataoffset +
                                  skip*(numBatches - 1) +
                                  qwBatchSize + 1;
                fieldRec[CL_MAXCL+1].reverse = lastReverse;
                lastReverse += qwBatchSize
                    - ((3*vertsEndCnt + 15) >> 4);
            }
#endif /* FASTMORPH */
            /* We now need to move the non-opaque data down by lastReverse */
            nonOpaqueOffset -= lastReverse;

            /* Add size of non opaque data to its start offset to get total size */
            size += nonOpaqueOffset;
#ifdef DMAALIGN
            /* We don't do this here as it complicates things to no benifit */
#endif /* DMAALIGN */
            /* We now need to set up the dataoffset for the non-opaque */
#ifndef FASTMORPH
            for (i=0; i<((int)(CL_MAXCL)); i++)
#else /* !FASTMORPH */
            for (i=0; i<((int)(CL_MAXCL + fastMorphing)); i++)
#endif /* !FASTMORPH */
            {
                if (pvtdata->clinfo[i].attrib & CL_ATTRIB_REQUIRED)
                {
                    if (!(pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE))
                    {
#ifndef FASTMORPH
                        fieldRec[i].dataoffset = nonOpaqueOffset;
#else /* !FASTMORPH */
                        if (fastMorphing)
                        {
                            fieldRec[i].morphDataoffset = nonOpaqueOffset;
                        }
                        else
                        {
                            fieldRec[i].dataoffset = nonOpaqueOffset;
                        }
#endif /* !FASTMORPH */
#ifdef REDEBUG
                        printf("field %d offset at %d\n", i, nonOpaqueOffset);
#endif /* REDEBUG */

                        nonOpaqueOffset +=
                            ((numVerts*
                              (pvtdata->clinfo[i].stride) + 3) >> 2);
                    }
                }
            }
        }

#ifdef REDEBUG
        printf("Computed size in qw: %d\n", size);
#endif /* REDEBUG */
        /* convert to bytes */
        size <<= 4;

        /* Add size of header */
        size += (sizeof(rwPS2ResEntryHeader)+15) & ~0xf;
#ifdef REDEBUG
        printf("+ sizeof(rwPS2ResEntryHeader) in bytes: %d\n", size);
#endif /* REDEBUG */

        /* ensure that we fill the final cache line */
        size += 127;
#ifdef REDEBUG
        printf("+ 127: %d\n", size);
#endif /* REDEBUG */
    }

    /* if (!*repEntry) */
    RWASSERT(!*repEntry);
    {
        u_long128 *data;
        u_long128 *lastTag;
        /* NOTE: initialising 128-bit ints doesn't work on most compilers,
         * These just save us a compile warning! (hope it doesn't cost us...) */
        u_long128 longZero = 0;
        u_long128 ltmp = 0;
        unsigned long tmp, tmp1;
        RwUInt32 sizeOnVU = (RwUInt32)pvtdata->sizeOnVU;
        RwBool wroteATag;
        int offset;

        /* Alloc space and add dma and vif tags */
#ifdef REDEBUG
        printf("Allocing res entry of size %d bytes\n", size);
#endif /* REDEBUG */

        /* Choose an object pointer. Its only going to turn into a void* */
        if (DMASessionRecord->objType == rxOBJTYPE_IM3D)
        {
            /* Im3D changes every frame (for now no caching), so don't use
             * a resEntry (fake one), use Malloc. We mark this memory to be
             * freed with _sweAddPkt() in PS2MatBridge at the end of
             * _rwRabinsBridgeNodeCode(). You could also use circularMalloc
             * with (SWE_PKT_CIRCALLOC | SWE_PKT_LOCAL) flags */
#if 0
            *repEntry = RwMalloc(sizeof(RwResEntry) + size);
#else
            *repEntry = (RwResEntry *)
                circularMalloc(circAllocator, 
                               sizeof(RwResEntry) + size,
                               calDCACHE_ALLOC);
#endif
            RWASSERT(*repEntry != NULL);

#ifdef REDEBUG
            if (*repEntry == NULL)
            {
                printf("Im3D DMA memory couldn't be alloc'd. Exiting...\n\n");
            }
#endif /* REDEBUG */

            /* We have to set up this space as if it were a resentry
             * cos later pipe code assumes this is done... for now */
            (*repEntry)->link.prev = (RwLLLink *)NULL;
            (*repEntry)->link.next = (RwLLLink *)NULL;
            (*repEntry)->owner = DMASessionRecord->sourceObject.agnostic;
            (*repEntry)->size = size;
            (*repEntry)->ownerRef = (RwResEntry **)NULL;
            (*repEntry)->destroyNotify = (void (*)(RwResEntry *))NULL;
            /* We mark the memory to be freed with _sweAddPkt() in
             * matBridge, at the end of _rwRabinsBridgeNodeCode() */
        }
        else
        {
            RwResEntry  *result;

            if (DMASessionRecord->objType == rxOBJTYPE_ATOMIC)
            {
                RpGeometry  *geom;

                geom = RpAtomicGetGeometry(DMASessionRecord->sourceObject.atomic);
                if (!fastMorphing)
                {
                    if (geom->instanceFlags & rpGEOMETRYINSTANCE)
                    {
                        result = _rwResourcesAllocateResEntry(
                                 geom, repEntry, size, rwMEMORYPOOLDEFAULT, reDestroyCallback);
                    }
                    else
                    {
                        result = RwResourcesAllocateResEntry(
                                 geom, repEntry, size, reDestroyCallback);
                    }
                }
                else
                {
                    result = RwResourcesAllocateResEntry(
                             DMASessionRecord->sourceObject.atomic,
                             repEntry, size, reDestroyCallback);
                }
            }
            else
            {
                result = RwResourcesAllocateResEntry(
                         DMASessionRecord->sourceObject.worldSector,
                         repEntry, size, reDestroyCallback);
            }
            
            RWASSERT(NULL != result);
#ifdef REDEBUG
            if (NULL == result)
            {
                printf("ResEntry couldn't be alloc'd. Exiting...\n\n");
            }
#endif /* REDEBUG */
        }

#ifdef REDEBUG
        printf("ResEntry at %p\n", *repEntry);
#endif /* REDEBUG */
#ifdef CLEARMEM
        {
            /* We clear memory to a value which is a VIF NOP, with a */
            /* stamp value in the bottom 16 bits */
            unsigned int *ptr;
            unsigned int *ptrEnd;

            ptr = (unsigned int *)(*repEntry + 1);
            ptrEnd = (unsigned int *)((char *)(*repEntry + 1) + size);
            while (ptr < ptrEnd)
                *ptr++ = (unsigned int)clearMemVal;
            clearMemVal++;
        }
#endif /* CLEARMEM */

        ps2ResHeader = (rwPS2ResEntryHeader*)(*repEntry + 1);

        ps2ResHeader->refCnt = 0;
        ps2ResHeader->clrCnt = 0;

        /* We specifically store an out of date serial number here
         * to force SyncDCache in matBridge - it's in there so it
         * happens as late as possible (the later the better, as
         * more data will have been flushed accidentally for free) */
        DMASessionRecord->serialNum = meshHeader->serialNum - 1;

        ps2ResHeader->serialNum = meshHeader->serialNum;
        ps2ResHeader->overloadFlags = overloadFlags;
        ps2ResHeader->numVerts = numVerts;
#ifndef FASTMORPH
        for (i=0; i<((int)(CL_MAXCL)); i++)
#else
        for (i=0; i<((int)(CL_MAXCL + fastMorphing)); i++)
#endif /* !FASTMORPH */
        {
            ps2ResHeader->fieldRec[i] = fieldRec[i];
        }
        ps2ResHeader->batchSize = batchSize;
        ps2ResHeader->batchesPerTag = batchesPerTag;
        ps2ResHeader->numBatches = numBatches;

#if (defined(FASTMORPH))
        /* These get overwritten by atomicreinstance but let's
         * at least initialise them for worldsectors/im3d */
        ps2ResHeader->morphStart = 0;
        ps2ResHeader->morphFinish = 0;
        ps2ResHeader->morphNum = 1;
#endif /* (defined(FASTMORPH)) */

        /* Cache the mesh type so we can test to see if it changes
           (yeah, it's paranoid to think mesh type might change
           while numverts stays the same, but...) */
        ps2ResHeader->meshType = meshHeader->flags;

        /* Cache-line-align the start of data in all circumstances */
        data = (u_long128 *)(((unsigned int)(ps2ResHeader + 1) + 63) & ~63);
#ifdef DMAALIGN
        if (pvtdata->totallyOpaque)
        {
            data = (u_long128*)(((unsigned int)data + 127) & ~0x7f);
        }
#endif /* DMAALIGN */
        ps2ResHeader->data = data;

        /* For all broken out clusters, set up the offset (from the base
         * of the instance data) so we don't have to access large data
         * structs during the fast-path [in envisionClusters()] */
        {
            RwUInt32 numBrokenOuts = 0;
#ifndef FASTMORPH
            for (i = 0;i < ((int)(CL_MAXCL));i++)
#else /* !FASTMORPH */
            for (i = 0;i < ((int)(CL_MAXCL + fastMorphing));i++)
#endif /* !FASTMORPH */
            {
                if (  (pvtdata->clinfo[i].attrib & CL_ATTRIB_REQUIRED) &&
                      (!(pvtdata->clinfo[i].attrib & CL_ATTRIB_OPAQUE) )    )
                {
                    offset = ps2ResHeader->fieldRec[i].dataoffset;
#ifdef FASTMORPH
                    if (fastMorphing)
                    {
                        offset = ps2ResHeader->fieldRec[i].morphDataoffset;
                    }
#endif /* FASTMORPH */
                    ps2ResHeader->clquickinfo[numBrokenOuts].data =
                        ps2ResHeader->data + offset;

                    ps2ResHeader->clquickinfo[numBrokenOuts].stride =
                        pvtdata->clinfo[i].stride << 2;
                    numBrokenOuts++;
                }
            }
            RWASSERT(numBrokenOuts == pvtdata->numBreakOuts);
        }

        MAKE128(longZero, 0l, 0l);

#ifdef REDEBUG
        printf("ps2ResHeader:\n");
        printf(" refCnt %d\n", ps2ResHeader->refCnt);
        printf(" clrCnt %d\n", ps2ResHeader->clrCnt);
        printf(" data %p\n", ps2ResHeader->data);
        printf(" serialNum %d\n", (RwUInt32)ps2ResHeader->serialNum);
        printf(" overloadFlags %d\n", (RwUInt32)ps2ResHeader->overloadFlags);
        printf(" numVerts %d\n", ps2ResHeader->numVerts);
        printf(" batchSize %d\n", ps2ResHeader->batchSize);
        printf(" batchesPerTag %d\n", ps2ResHeader->batchesPerTag);
        printf(" numBatches %d\n", ps2ResHeader->numBatches);
#ifdef FASTMORPH
        printf(" morphStart %d\n", ps2ResHeader->morphStart);
        printf(" morphFinish %d\n", ps2ResHeader->morphFinish);
        printf(" morphNum %d\n", ps2ResHeader->morphNum);
#endif /* FASTMORPH */
        {
            int ze = 0;
            volatile int *vol = &ze;

#ifndef FASTMORPH
            for (i = 0; i< ((int)(CL_MAXCL)); i++)
#else /* !FASTMORPH */
            for (i = 0; i< ((int)(CL_MAXCL + 2)); i++)
#endif /* !FASTMORPH */
            {
                printf(" fieldRec %d\n", i);
                printf("  numVerts %d\n", ps2ResHeader->fieldRec[i].numVerts);
#ifdef FASTMORPH
                printf("  morphNumVerts %d\n", ps2ResHeader->fieldRec[i].morphNumVerts);
#endif /* FASTMORPH */
                printf("  dataoffset %d\n", ps2ResHeader->fieldRec[i].dataoffset);
#ifdef FASTMORPH
                printf("  morphDataoffset %d\n", ps2ResHeader->fieldRec[i].morphDataoffset);
#endif /* FASTMORPH */
                printf("  skip %d\n", ps2ResHeader->fieldRec[i].skip);
#ifdef FASTMORPH
                printf("  morphSkip %d\n", ps2ResHeader->fieldRec[i].morphSkip);
#endif /* FASTMORPH *
                printf("  reverse %d\n", ps2ResHeader->fieldRec[i].reverse);
                printf("  vuoffset %d\n", ps2ResHeader->fieldRec[i].vuoffset);
                /* We put a pause in here as dsnetm has a buffer overrun problem */
                for (j=0; j<100000; j++)
                {
                    if (*vol)
                    {
                        printf("Odd?\n");
                    }
                }
            }
        }
#endif /* REDEBUG */

        /* Fill in the DMA tags in the chain */
        lastTag = data;
        wroteATag = FALSE;
        for (i = 0; i < (int)numBatches; i++)
        {
            int currentBatchSize;

            if (i < (int)numBatches - 1)
            {
                currentBatchSize = batchSize;
            }
            else
            {
                currentBatchSize =
                    effectiveTotalVerts - (numBatches - 1)*batchSize;
            }
            /* Insert a dma tag if required, and fix up last one */
            if (i%batchesPerTag == 0)
            {
#if 0
                /* Later instancing code may not know about this yet, so we leave it out */
#ifdef DMAALIGN
                if ((pvtdata->totallyOpaque) && ((int)data & 0x70))
                {
                    *data++ = longZero;
                }
#endif /* DMAALIGN */
#endif
                tmp = (1 << 28) | (data - lastTag - 1);
                if (i==0)
                {
                    /* NOTE: the comment avoids masking path three, so we
                     * are free to do asynchronous texture uploads using it. */
                    tmp1 = ((0x06l << 24 /*| 0x8000l*/) << 32) | (0x11l << 24);
                }
                else
                {
                    tmp1 = 0;
                }
                MAKE128(ltmp, tmp1, tmp);
                
                if ((pvtdata->totallyOpaque) && (i != 0))
                {
                    /* Only overwrite the bottom 64 bits in the all opaque case!
                     * We have a VIF unpack command in the top 64 bits. */
                   *(long *)lastTag = tmp;
                    lastTag = data;
                    wroteATag = TRUE;
                }
                else
                {
                    /* In the non-totally-opaque case, we need
                     * zeroes to be written in the top 64 bits. */
                   *lastTag = ltmp;
                    lastTag = data++;
                }
            }
            /* first do striped clusters */
#ifndef FASTMORPH
            for (j = 0; j < ((int)(CL_MAXCL)); j++)
#else /* !FASTMORPH */
            for (j = 0; j < ((int)(CL_MAXCL + fastMorphing)); j++)
#endif /* !FASTMORPH */
            {
                if (pvtdata->clinfo[j].attrib & CL_ATTRIB_REQUIRED)
                {
                    if (!(pvtdata->clinfo[j].attrib & CL_ATTRIB_OPAQUE))
                    {
                        int __size;

                        switch (pvtdata->clinfo[j].attrib & CL_TYPE_MASK
                                & ~CL_USN)
                        {
                            case CL_S32:
                            case CL_V4_8:
                            case CL_V2_16:
                                __size = 1;
                                break;
                            case CL_V2_32:
                            case CL_V4_16:
                                __size = 2;
                                break;
                            case CL_V3_32:
                                __size = 3;
                                break;
                            case CL_V4_32:
                            default:
                                __size = 4;
                                break;
                        }
                        offset = ps2ResHeader->fieldRec[j].dataoffset;
#ifdef FASTMORPH
                        if (fastMorphing)
                        {
                            offset = ps2ResHeader->fieldRec[j].morphDataoffset;
                        }
#endif /* FASTMORPH */
                        tmp = (3 << 28) | ((currentBatchSize*__size + 3) >> 2) |
                            (((long)(int)(ps2ResHeader->data + offset) +
                              4*i*(batchSize - ((meshHeader->flags & rpMESHHEADERTRISTRIP)?2:0))*__size)<<32);
                        /* We must upload >= the amount of ref'd data */
                        tmp1 = (1 << 24) | (1 << 8) | (sizeOnVU) |
                            (((((long)pvtdata->clinfo[j].attrib) & CL_TYPE_MASK)|
                              ((currentBatchSize +
                                /* amount to qw + round term / size */
                                (((((currentBatchSize*__size + 3) & ~0x3)-
                                   (currentBatchSize*__size)) + (__size - 1)) / __size)) << 16) |
                              0x8000 | ps2ResHeader->fieldRec[j].vuoffset) << 32);

                        MAKE128(ltmp, tmp1, tmp);
                        *lastTag = ltmp;

                        /* Cnt 0 for return */
                        tmp = (1 << 28);
                        MAKE128(ltmp, 0l, tmp);
                        *data++  = ltmp;
                        lastTag = data++;
                    }
                }
            }
            /* Now do opaque clusters */
#ifndef FASTMORPH
            for (j = 0; j < ((int)(CL_MAXCL)); j++)
#else /* !FASTMORPH */
            for (j = 0; j < ((int)(CL_MAXCL + fastMorphing)); j++)
#endif /* FASTMORPH */
            {
                if (pvtdata->clinfo[j].attrib & CL_ATTRIB_REQUIRED)
                {
                    if (pvtdata->clinfo[j].attrib & CL_ATTRIB_OPAQUE)
                    {
                        /* We back fill the last qw of each block */
                        /* with zero so we won't have to do so each time */
                        switch (j)
                        {
                            case CL_XYZ:
                                if (wroteATag)
                                {
                                    tmp = *(long *)data;
                                    wroteATag = FALSE;
                                }
                                else
                                {
                                    tmp = ((5l << 24) << 32) | DEBUGMARK();
                                }
                                tmp1 = (1 << 24) | (1 << 8) | (sizeOnVU) |
                                    (((0x68l << 24) | (currentBatchSize << 16)
                                      | 0x8000 | ps2ResHeader->fieldRec[j].vuoffset) << 32);
                                MAKE128(ltmp, tmp1, tmp);
                                *data = ltmp;
                                data += (3*currentBatchSize + 3) >> 2;
                                *data++ = longZero;
                                break;
                            case CL_UV:
                                if (wroteATag)
                                {
                                    tmp = *(long*)data;
                                    wroteATag = FALSE;
                                }
                                else
                                {
                                    tmp = ((5l << 24) << 32) | DEBUGMARK();
                                }
                                tmp1 = (1 << 24) | (1 << 8) | (sizeOnVU)|
                                    (((0x64l << 24) | (currentBatchSize << 16)
                                      | 0x8000 |  ps2ResHeader->fieldRec[j].vuoffset) << 32);
                                MAKE128(ltmp, tmp1, tmp);
                                *data = ltmp;
                                data += (2*currentBatchSize + 3) >> 2;
                                *data++ = longZero;
                                break;
                            case CL_UV2:
                                if (wroteATag)
                                {
                                    tmp = *(long*)data;
                                    wroteATag = FALSE;
                                }
                                else
                                {
                                    tmp = ((5l << 24) << 32) | DEBUGMARK();
                                }
                                tmp1 = (1 << 24) | (1 << 8) | (sizeOnVU)|
                                    (((0x6cl << 24) | (currentBatchSize << 16)
                                      | 0x8000 |  ps2ResHeader->fieldRec[j].vuoffset) << 32);
                                MAKE128(ltmp, tmp1, tmp);
                                *data = ltmp;
                                data += (4*currentBatchSize + 3) >> 2;
                                *data++ = longZero;
                                break;
                            case CL_RGBA:
                                if (wroteATag)
                                {
                                    tmp = *(long*)data;
                                    wroteATag = FALSE;
                                }
                                else
                                {
                                    tmp = ((5l << 24) << 32) | DEBUGMARK();
                                }
                                tmp1 = (1 << 24) | (1 << 8) | (sizeOnVU)|
                                    (((0x6el << 24) | (currentBatchSize << 16)
                                      | 0xc000 |  ps2ResHeader->fieldRec[j].vuoffset) << 32);
                                MAKE128(ltmp, tmp1, tmp);
                                *data = ltmp;
                                data += (currentBatchSize + 3) >> 2;
                                *data++ = longZero;
                                break;
                            case CL_NORMAL:
                                if (wroteATag)
                                {
                                    tmp = *(long*)data;
                                    wroteATag = FALSE;
                                }
                                else
                                {
                                    tmp = ((5l << 24) << 32) | DEBUGMARK();
                                }
                                tmp1 = (1 << 24) | (1 << 8) | (sizeOnVU)|
                                    (((0x6al << 24)
                                      | (currentBatchSize << 16)
                                      | 0x8000 | ps2ResHeader->fieldRec[j].vuoffset)
                                     << 32);
                                MAKE128(ltmp, tmp1, tmp);
                                *data = ltmp;
                                data += (3*currentBatchSize + 15) >> 4;
                                *data++ = longZero;
                                break;
#ifdef FASTMORPH
                            case CL_MAXCL:
                                if (wroteATag)
                                {
                                    tmp = *(long *)data;
                                    wroteATag = FALSE;
                                }
                                else
                                {
                                    tmp = ((5l << 24) << 32) | DEBUGMARK();
                                }
                                tmp1 = (1 << 24) | (4 << 8) | (4) |
                                    (((0x68l << 24) | (currentBatchSize << 16)
                                      | 0x8000 | (currentBatchSize*sizeOnVU))
                                     << 32);
                                MAKE128(ltmp, tmp1, tmp);
                                *data = ltmp;
                                data += (3*currentBatchSize + 3) >> 2;
                                *data++ = longZero;
                                break;
                            case CL_MAXCL+1:
                                if (wroteATag)
                                {
                                    tmp = *(long*)data;
                                    wroteATag = FALSE;
                                }
                                else
                                {
                                    tmp = ((5l << 24) << 32) | DEBUGMARK();
                                }
                                tmp1 = (1 << 24) | (4 << 8) | (4)|
                                    (((0x6al << 24)
                                      | (currentBatchSize << 16)
                                      | 0x8000 | (currentBatchSize*(sizeOnVU+1)))
                                     << 32);
                                MAKE128(ltmp, tmp1, tmp);
                                *data = ltmp;
                                data += (3*currentBatchSize + 15) >> 4;
                                *data++ = longZero;
                                break;
#endif /* FASTMORPH */
                        }
                    }
                }
            }

            /* Now do ITOP an run/cont */
#ifdef VUCONTINUE
            tmp = ((0x17l << 24)) <<32;
#else /* VUCONTINUE */
            tmp = (((i == 0)?(0x15l << 24):(0x17l << 24)) <<32);
            /* For all supported primitive types (polylines get converted
             * to linelists CPU-side and trifans to trilists, otherwise
             * tristrips/trilists/linelists are supported and pointlists
             * are supported as long as appropriate VU code is supplied),
             * our counter is the number of verts uploaded per batch. */
#endif /* VUCONTINUE */
            tmp |= (4 << 24) | currentBatchSize;

            MAKE128(ltmp, 0l, tmp);
            *data++ = ltmp;
        }
#ifdef VUCONTINUE
        *(((unsigned long *)data) - 1) = (0x10l << 24) | ((0x06l << 24 | 0x0000l) << 32);
#else /* VUCONTINUE */
        /* We make the very last vif commands FLUSH/unmask3 */
        *(((unsigned long *)data) - 1) = (0x11l << 24) | ((0x06l << 24 | 0x0000l) << 32);
#endif /* VUCONTINUE */

        /* return */
        tmp = (6 << 28) | (data - lastTag - 1);
        *(long *)lastTag = tmp;
        if (!(pvtdata->totallyOpaque))
        {
            *((long *)lastTag + 1) = 0l;
        }
#ifdef REDEBUG
        finaladdr = data;
        printf("Final addr = %p (size = %d qws)\n", finaladdr, finaladdr-ps2ResHeader->data);
#endif /* REDEBUG */
    }

#ifdef REDEBUG
#ifdef DMADUMP
    /* dump the entire structure to stdout */
    {
        int ze = 0;
        volatile int *vol = &ze;
        u_long128 *ptr;

        /* Now dump the data */
        ptr = ps2ResHeader->data;
        for (;(int)ptr < finaladdr; ptr++)
        {
            printf("%x: %.16lx %.16lx\n", (int)ptr, *((long *)ptr + 1), *(long *)ptr);
            /* We put a pause in here as dsnetm has a buffer overrun problem */
            for (j = 0; j < 100000; j++)
            {
                if (*vol)
                {
                    printf("Odd?\n");
                }
            }

        }
    }
#endif /* DMADUMP */
#endif /* REDEBUG */

    /* At this point we can now actually copy in the object data */
    if (pvtdata->numBreakOuts > 0)
    {
        envisionClusters(pvtdata, ps2ResHeader);
    }

    if (DMASessionRecord->objType == rxOBJTYPE_WORLDSECTOR)
    {
#ifdef REDEBUG
        printf("Exiting via reInstanceFromWorldSector\n\n");
#endif /* REDEBUG */
        RWRETURN(reInstanceFromWorldSector(pvtdata, 
                                           ps2ResHeader, 
                                           mesh,
                                           (RpMeshHeaderFlags)meshHeader->flags,
                                           numVerts, 
                                           DMASessionRecord));
    }
    else if (DMASessionRecord->objType == rxOBJTYPE_ATOMIC)
    {
#ifdef REDEBUG
        printf("Exiting via reInstanceFromAtomic\n\n");
#endif /* REDEBUG */
        RWRETURN(reInstanceFromAtomic(pvtdata, 
                                      ps2ResHeader, 
                                      mesh,
                                      (RpMeshHeaderFlags)meshHeader->flags,
                                      numVerts, 
                                      needToReInstance, 
                                      DMASessionRecord));
    }
    else /* (DMASessionRecord->objType == rxOBJTYPE_IM3D) */
    {
#ifdef REDEBUG
        printf("Exiting via reInstanceFromIm3D\n\n");
#endif /* REDEBUG */
        RWRETURN(reInstanceFromIm3D(ps2ResHeader, 
                                    mesh,
                                    (RpMeshHeaderFlags)meshHeader->flags,
                                    numVerts, 
                                    DMASessionRecord));
    }
    /* No code here to ensure tail optimisations happen */
}

/****************************************************************************
 _rwPS2MatInstancePipelineNodeInit()

 called at construction time
 */
RwBool
_rwPS2MatInstancePipelineNodeInit(RxPipelineNode *self)
{
    RwBool result = FALSE;
    rwPS2MatInstancePvtData  *pvtdata;
    rwPS2MatInstanceInitData *initData;
    RwUInt32 numRequiredClusters = 0;

    RWFUNCTION(RWSTRING("_rwPS2MatInstancePipelineNodeInit"));

    RWASSERT( self != NULL );

    pvtdata  = (rwPS2MatInstancePvtData *) self->privateData;
    initData = (rwPS2MatInstanceInitData *)RxPipelineNodeGetInitData(self);

    RWASSERT( pvtdata != NULL );

    if (initData != NULL)
    {
        RwUInt32 CL_code;

        pvtdata->checkSizeOnVU     = initData->checkSizeOnVU;
        pvtdata->vu1MaxTSInputSize = initData->vu1MaxTSInputSize;
        pvtdata->vu1MaxTLInputSize = initData->vu1MaxTLInputSize;
        pvtdata->vu1MaxPLInputSize = initData->vu1MaxPLInputSize;
        pvtdata->pipeType          = initData->pipeType;
        if (pvtdata->pipeType == 0)
        {
            /* Default to triangles/lines, not points */
            pvtdata->pipeType = (RpMeshHeaderFlags) 
                (rpMESHHEADERTRISTRIP|rpMESHHEADERTRIFAN|
                 rpMESHHEADERLINELIST|rpMESHHEADERPOLYLINE);
        }

        pvtdata->numBreakOuts = 0;
        for (CL_code = 0;CL_code < ((int)(CL_MAXCL));CL_code++)
        {
            RxClusterDefinition *cluster = initData->clusters[CL_code];

            if (cluster != NULL)
            {
                numRequiredClusters++;

                /* update instancing data (pvtdata->clinfo) for all required clusters
                 * (required means at the very least that space for instance data is
                 * created) and fastpath data (ps2ResHeader->clquickinfo) for all
                 * clusters to be exposed to nodes in the pipeline for reading/writing */

                if (cluster->defaultAttributes & CL_ATTRIB_REQUIRED)
                {
                    /* determine the extent to which instancing needs to account for this cluster */
                    pvtdata->clinfo[CL_code].attrib = cluster->defaultAttributes;

                    if (cluster->defaultAttributes & CL_ATTRIB_PLACEHOLDER)
                    {
                        RwChar string[256];
                        rwsprintf(string,
                                  "_rwPS2MatInstancePipelineNodeInit; cluster %s has both CL_ATTRIB_REQUIRED and CL_ATTRIB_PLACEHOLDER set in its attributes - illegal!",
                                  cluster->name);
                        MESSAGE(string);
                        RWRETURN(FALSE);
                    }

                    /* Calculate stride here to negate the need for switch statements at run-time */
                    switch (pvtdata->clinfo[CL_code].attrib & CL_TYPE_MASK
                            & ~CL_USN)
                    {
                        case CL_S32:
                        case CL_V4_8:
                        case CL_V2_16:
                            pvtdata->clinfo[CL_code].stride = 1;
                            break;
                        case CL_V2_32:
                        case CL_V4_16:
                            pvtdata->clinfo[CL_code].stride = 2;
                            break;
                        case CL_V3_32:
                            pvtdata->clinfo[CL_code].stride = 3;
                            break;
                        case CL_V4_32:
                        default:
                            pvtdata->clinfo[CL_code].stride = 4;
                            break;
                    }

                    /* If the cluster should be visible in the pipeline we
                       need to flag it so an actual RxCluster is created for
                       it (we need to know which clusterofinterest it is too) */
                    if (cluster->defaultAttributes & CL_ATTRIB_READWRITE)
                    {
                        RwUInt32 cliIndex = 0;

                        if (cluster->defaultAttributes & CL_ATTRIB_OPAQUE)
                        {
                            RwChar string[256];
                            rwsprintf(string,
                                      "_rwPS2MatInstancePipelineNodeInit; cluster %s has both (CL_ATTRIB_READ or CL_ATTRIB_WRITE) and CL_ATTRIB_OPAQUE set, these are mutually incompatible",
                                      cluster->name);
                            MESSAGE(string);
                            RWRETURN(FALSE);
                        }

                        while (self->nodeDef->io.clustersOfInterest[cliIndex].clusterDef
                               != cluster)
                        {
                            if (cliIndex >= self->nodeDef->io.numClustersOfInterest)
                            {
                                RwChar string[256];
                                rwsprintf(string,
                                          "_rwPS2MatInstancePipelineNodeInit; could not locate cluster %s in node definition clusters of interest",
                                          cluster->name);
                                MESSAGE(string);
                                RWRETURN(FALSE);
                            }
                            cliIndex++;
                        }

                        /* Check to make sure this cluster exists in the pipe (i.e
                         * there is a user node that requests it): */
                        if (self->inputToClusterSlot[cliIndex] == (RwUInt32)-1)
                        {
                            RwChar string[256];
                            rwsprintf(string,
                                      "Cluster %s not present (subsequent nodes don't require it). You should not be requiring this cluster to be visible (CL_ATTRIB_READ or CL_ATTRIB_WRITE) in its creation attributes [read during the call to RxPS2MatInstanceGenerateCluster()]",
                                      cluster->name);
                            MESSAGE(string);
                            RWRETURN(FALSE);
                        }

                        pvtdata->cliIndex[pvtdata->numBreakOuts] = cliIndex;
                        pvtdata->numBreakOuts++;
                    }
                    else /*if (0)*/
                    {
                        /* User clusters not set to READWRITE are meaningless,
                         * the instance node won't fill 'em, the bridge node
                         * won't upload 'em and no nodes be able to access 'em! */
                        if (CL_code >= CL_USER1)
                        {
                            RwChar string[256];
                            rwsprintf(string,
                                      "_rwPS2MatInstancePipelineNodeInit; user clusters must have attributes set to CL_ATTRIB_PLACEHOLDER or CL_ATTRIB_WRITE or they will have no effect!",
                                      cluster->name);
                            MESSAGE(string);
                            RWRETURN(FALSE);
                        }
                        else
                        {
                            /* Standard clusters set to DONTFILL and not set to
                             * WRITE and not set to PLACEHOLDER are invalid - we'd
                             * just be uploading uninitialised junk, which is silly. */
                            if ( (cluster->defaultAttributes &
                                  CL_ATTRIB_DONT_FILL) )
                            {
                                RwChar string[256];
                                rwsprintf(string,
                                          "_rwPS2MatInstancePipelineNodeInit; non-user clusters with CL_ATTRIB_DONTFILL attributes must also have either CL_ATTRIB_PLACEHOLDER or CL_ATTRIB_WRITE attributes, or uninitialised junk will be uploaded to VU1",
                                          cluster->name);
                                MESSAGE(string);
                                RWRETURN(FALSE);
                            }
                        }
                    }
                }
                else if (cluster->defaultAttributes & CL_ATTRIB_PLACEHOLDER)
                {
                    /* instancing just needs to reserve space VU-side for this cluster */
                    pvtdata->clinfo[CL_code].attrib = cluster->defaultAttributes;

                    /* Calculate stride here to negate the need for switch statements at run-time */
                    switch (pvtdata->clinfo[CL_code].attrib & CL_TYPE_MASK
                            & ~CL_USN)
                    {
                        case CL_S32:
                        case CL_V4_8:
                        case CL_V2_16:
                            pvtdata->clinfo[CL_code].stride = 1;
                            break;
                        case CL_V2_32:
                        case CL_V4_16:
                            pvtdata->clinfo[CL_code].stride = 2;
                            break;
                        case CL_V3_32:
                            pvtdata->clinfo[CL_code].stride = 3;
                            break;
                        case CL_V4_32:
                        default:
                            pvtdata->clinfo[CL_code].stride = 4;
                            break;
                    }
                }
                else
                {
                    /* This cluster is present but has weird flags... fail and warn */
                    RwChar string[256];
                    RwChar string2[32];

                    GETCLCODESTRING(CL_code, string2);
                    rwsprintf(string,
                              "_rwPS2MatInstancePipelineNodeInit; %s cluster has unrecognised attributes",
                              string2);
                    MESSAGE(string);
                    RWRETURN(FALSE);
                }
            }
            else
            {
                /* Instancing doesn't need to deal with this cluster at all */
                pvtdata->clinfo[CL_code].attrib = 0;
            }

        }
    }
    else
    {
        /* Set up default VU sizes
           (this will be executed once zero clusters is supported) */
        pvtdata->checkSizeOnVU = 4;
        pvtdata->vu1MaxTSInputSize = VU1_MAX_TS_INPUT_SIZE;
        pvtdata->vu1MaxTLInputSize = VU1_MAX_TL_INPUT_SIZE;
        pvtdata->vu1MaxPLInputSize = VU1_MAX_PL_INPUT_SIZE;
        pvtdata->pipeType          = (RpMeshHeaderFlags)
            (rpMESHHEADERTRISTRIP|rpMESHHEADERTRIFAN|
             rpMESHHEADERLINELIST|rpMESHHEADERPOLYLINE);
    }

    if ((initData            == NULL) ||
        (numRequiredClusters <= 0   )    )
    {
        MESSAGE("_rwPS2MatInstancePipelineNodeInit; zero clusters generated! (currently not allowed)");
        RWRETURN(FALSE);
    }

    result = RabinsConstructionTimeCode(pvtdata); /* success? */

    RWRETURN(result);
}

/****************************************************************************
 PS2MatInstanceNodeBody()
 */
static RwBool
PS2MatInstanceNodeBody(RxPipelineNodeInstance *self,
                       const RxPipelineNodeParam * __RWUNUSED__ params)
{
    rwPS2MatInstancePvtData *pvtdata;
    RxPacket  *pk;
    RxCluster *clDMASessionRecord;
    RxCluster *clMesh;
    RwUInt32   i;
    RwBool     success;

    RWFUNCTION(RWSTRING("PS2MatInstanceNodeBody"));

    RWASSERT(NULL != self);

    pvtdata = (rwPS2MatInstancePvtData *) self->privateData;
    RWASSERT(NULL != pvtdata);

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

    clDMASessionRecord = RxClusterLockRead(pk, 0);
    RWASSERT((NULL != clDMASessionRecord) &&
             (clDMASessionRecord->numUsed > 0));
    clMesh             = RxClusterLockRead(pk, 1);
    RWASSERT((NULL != clMesh) &&
             (clMesh->numUsed > 0));
    
    /* now lockwrite() breakout clusters */
    for (i = 0;i < pvtdata->numBreakOuts;i++)
    {
        pvtdata->clusters[i] =
            RxClusterLockWrite(pk, pvtdata->cliIndex[i], self);

        /* User nodes in the pipeline should be requesting this 
         * cluster (which will ensure it's present here). 
         * If not why are they asking us to make it visible?? */
        /* Assert presence of cluster of interest */
        RWASSERT(NULL != pvtdata->clusters[i]);
    }

    success = _rwRabinsMatInstanceCode(
        (RxPS2DMASessionRecord *) clDMASessionRecord->data,
        (RxPS2Mesh *) clMesh->data,
        pvtdata);
    RWASSERT(FALSE != success);

    RxPacketDispatch(pk, 0 /* DefaultOutput */, self);

    RWRETURN(TRUE);
}

/**********************************************************************
 PS2MatInstanceCreateInitData
 */
static RwBool
PS2MatInstanceCreateInitData(RxPipelineNode *self)
{
    rwPS2MatInstanceInitData *data;

    RWFUNCTION(RWSTRING("PS2MatInstanceCreateInitData"));

    data = (rwPS2MatInstanceInitData *)
        RxPipelineNodeCreateInitData(
            self, sizeof(rwPS2MatInstanceInitData));
    if (data != NULL)
    {
        RwUInt32 i;
        for (i = 0;i < ((int)(CL_MAXCL));i++)
        {
            data->clusters[i] = (RxClusterDefinition *)NULL;
            data->checkSizeOnVU = 4;
            data->vu1MaxTLInputSize = VU1_MAX_TL_INPUT_SIZE;
            data->vu1MaxTSInputSize = VU1_MAX_TS_INPUT_SIZE;
            data->vu1MaxPLInputSize = VU1_MAX_PL_INPUT_SIZE;
            data->pipeType = (RpMeshHeaderFlags)0;
        }
        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2MatInstanceGenerateCluster is used to
 * force this node to generate clusters. It should be called with a
 * pointer to this node and each of the RxClPS2* clusters required.
 * This function must be called before the pipeline containing this
 * node is unlocked because that is when cluster attributes are
 * finalized.
 *
 * These are the PS2-specific clusters you can request for this node
 * (see also \ref RxPS2ClusterType):
 *      \li RxClPS2xyz - Object-space position of vertices
 *      \li RxClPS2uv - Base texture coordinates of vertices
 *      \li RxClPS2uv2 - Two texture coordinate sets
 *      \li RxClPS2rgba - Prelight colors of vertices
 *      \li RxClPS2normal - Object-space normals at each vertex
 *      \li RxClPS2user1 - User-defined cluster 1
 *      \li RxClPS2user2 - User-defined cluster 2
 *      \li RxClPS2user3 - User-defined cluster 3
 *      \li RxClPS2user4 - User-defined cluster 4
 *
 * PS2-specific attributes (in the RxPS2AttributeSet namespace, identifier
 * string "PS2") are used to define two properties of the above PS2-specific
 * clusters. Firstly, the \ref RxPS2ClusterAttrib flags define how the data's
 * instancing into DMA chains is handled by PS2MatInstance.csl. Secondly, the
 * \ref RxPS2ClusterFormatAttrib flags define what format the data will be in
 * as interpreted by the DMA engine and VIF when the data is uploaded to VU1.
 * 
 * The format attributes of the first four of the above PS2-specific clusters
 * are fixed and you cannot change them, however the instance flags can be
 * changed. The user-defined clusters can have any attributes you please.
 * Since these cluster definitions are static globals and provided really as
 * examples, it is suggested that you create copies, modify their attributes
 * as appropriate and submit these copies to this function. See
 * \ref RxClusterSetAttributes for details on changing cluster attributes.
 *
 * Only clusters which are specified with CL_ATTRIB_READ or CL_ATTRIB_WRITE
 * will result in \ref RxCluster actually being present in packets flowing
 * down this pipeline. Other clusters are only present from the point of
 * view of the instancing and DMA code and you will not be able to access
 * them using \ref RxClusterLockRead or \ref RxClusterLockWrite. Visible
 * clusters will be 'broken out' from the usual (complex, packed) format of
 * DMA packet data such that their data is still accessible as a simple,
 * contiguous array of data just like non-PS2-specific clusters. However,
 * their data will always be flagged as rxCLFLAGS_EXTERNALMODIFIABLE because
 * it is in a fixed location with a fixed size - you cannot free or resize
 * it, but, if CL_ATTRIB_WRITE is set you can modify it. This is useful for
 * things like CPU-side particle system animation. For example, you might
 * request the CL_RGBA and CL_XYZ clusters and specify the CL_XYZ cluster as
 * CL_ATTRIB_READWRITE. Then a node placed after PS2MatInstance.csl and before
 * PS2MatBridge.csl could modify the positions of vertices (particles) before
 * they are uploaded to VU1 (remembering to flush cache on this data of
 * course!).
 *
 * You have to have at least one cluster and can have a maximum of eight
 * (with at most four user-defined clusters). Clusters currently have a
 * maximum stride of one quadword though this limitation will be removed
 * in the future.
 *
 * \return a pointer to the input pipeline node on success, otherwise NULL.
 *
 * \see RxClusterGetAttributes
 * \see RxClusterSetAttributes
 * \see RxPipelineClusterGetCreationAttributes
 * \see RxPipelineClusterSetCreationAttributes
 * \see RxPipelineNodePS2MatInstanceSetVUBufferSizes
 * \see RxPipelineNodePS2MatInstanceSetPointListVUBufferSize
 * \see RxPipelineNodePS2MatBridgeSetVIFOffset
 * \see RxPipelineNodePS2MatBridgeSetVU1CodeArray
 * \see RxNodeDefinitionGetPS2Manager
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2MatBridge
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2MatInstanceGenerateCluster(
    RxPipelineNode *self,                  /**< pointer to a PS2MatInstance.csl pipeline node */
    RxClusterDefinition *cluster2generate, /**< A pointer to a PS2-specific cluster definition */
    RwUInt32 type                          /**< PS2-specific cluster type specifier */
)
{
    RxPipelineNode *result = (RxPipelineNode *)NULL;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2MatInstanceGenerateCluster"));

    RWASSERT( self != NULL );
    RWASSERT( cluster2generate != NULL );

    if( (self != NULL) && (cluster2generate != NULL) )
    {
        /* The standard cluster types are non-editable */
        switch (type)
        {
        case CL_XYZ:
            {
                if ((!(cluster2generate->defaultAttributes & CL_V3_32)) ||
                    strcmp(cluster2generate->attributeSet, RxPS2AttributeSet))
                {
                    RWASSERT(cluster2generate->defaultAttributes & CL_V3_32);
                    RWASSERT(!strcmp(cluster2generate->attributeSet,
                                     RxPS2AttributeSet) );
                    MESSAGE("You may not change the data format of the standard RxClPS2xyz cluster. If you wish to change its attributes, omit it and insert a user cluster to replace it");
                    RWRETURN((RxPipelineNode *)NULL);
                }
                break;
            }
        case CL_UV:
            {
                if ((!(cluster2generate->defaultAttributes & CL_V2_32)) ||
                    strcmp(cluster2generate->attributeSet, RxPS2AttributeSet))
                {
                    RWASSERT(cluster2generate->defaultAttributes & CL_V2_32);
                    RWASSERT(!strcmp(cluster2generate->attributeSet,
                                     RxPS2AttributeSet) );
                    MESSAGE("You may not change the data format of the standard RxClPS2uv cluster. If you wish to change its attributes, omit it and insert a user cluster to replace it");
                    RWRETURN((RxPipelineNode *)NULL);
                }
                break;
            }
        case CL_UV2:
            {
                if ((!(cluster2generate->defaultAttributes & CL_V4_32)) ||
                    strcmp(cluster2generate->attributeSet, RxPS2AttributeSet))
                {
                    RWASSERT(cluster2generate->defaultAttributes & CL_V4_32);
                    RWASSERT(!strcmp(cluster2generate->attributeSet,
                                     RxPS2AttributeSet) );
                    MESSAGE("You may not change the data format of the standard RxClPS2uv2 cluster. If you wish to change its attributes, omit it and insert a user cluster to replace it");
                    RWRETURN((RxPipelineNode *)NULL);
                }
                break;
            }
        case CL_RGBA:
            {
                if ((!(cluster2generate->defaultAttributes & CL_V4_8)) ||
                    (!(cluster2generate->defaultAttributes & CL_USN )) ||
                    strcmp(cluster2generate->attributeSet, RxPS2AttributeSet))
                {
                    RWASSERT(cluster2generate->defaultAttributes & CL_V4_8);
                    RWASSERT(cluster2generate->defaultAttributes & CL_USN);
                    RWASSERT(!strcmp(cluster2generate->attributeSet,
                                     RxPS2AttributeSet) );
                    MESSAGE("You may not change the data format of the standard RxClPS2rgba cluster. If you wish to change its attributes, omit it and insert a user cluster to replace it");
                    RWRETURN((RxPipelineNode *)NULL);
                }
                break;
            }
        case CL_NORMAL:
            {
                if ((!(cluster2generate->defaultAttributes & CL_V4_8)) ||
                    strcmp(cluster2generate->attributeSet, RxPS2AttributeSet))
                {
                    RWASSERT(cluster2generate->defaultAttributes & CL_V4_8);
                    RWASSERT(!strcmp(cluster2generate->attributeSet,
                                     RxPS2AttributeSet) );
                    MESSAGE("You may not change the data format of the standard RxClPS2normal cluster. If you wish to change its attributes, omit it and insert a user cluster to replace it");
                    RWRETURN((RxPipelineNode *)NULL);
                }
                break;
            }
        }

        if( !strcmp(cluster2generate->attributeSet, RxPS2AttributeSet) )
        {
            /* we only need to generate clusters for data that
               needs to be read/written by nodes in the pipe */
            if (cluster2generate->defaultAttributes & CL_ATTRIB_READWRITE)
            {
                RxNodeDefinition *nodespec;

                nodespec = RxPipelineNodeCloneDefinition(self, cluster2generate);

                if ( nodespec != NULL ) /* RwNodeClone() succeeded? */
                {
                    /* Set cluster just added to RxNodeDefinition as valid on output.
                     * (make sure the node *has* an output though! PS2Manager doesn't
                     * and will call this func with self pointing at it) */
                    if (nodespec->io.numOutputs > 0)
                    {
                        nodespec->io.outputs[0].outputClusters
                            [nodespec->io.numClustersOfInterest - 1] = rxCLVALID_VALID;
                    }

                    result = self; /* success */
                }
                else
                {
                    MESSAGE("RxPipelineNodePS2MatInstanceGenerateCluster; RxPipelineNodeCloneDefinition failed");
                }
            }
            else
            {
                result = self;
            }

            if (result == self)
            {
                rwPS2MatInstanceInitData *data;

                data = (rwPS2MatInstanceInitData *)
                           RxPipelineNodeGetInitData(self);
                if (data == NULL)
                {
                    if (PS2MatInstanceCreateInitData(self) == FALSE)
                    {
                        RWRETURN((RxPipelineNode *)NULL);
                    }
                    data = (rwPS2MatInstanceInitData *)
                               RxPipelineNodeGetInitData(self);
                }

                /* We really don't want them setting a cluster twice, they get
                 * overwritten and I don't see why they need to do it. Besides,
                 * we'd have to replace the previously added clusterdef and it
                 * would be a real pain */
                RWASSERT(data->clusters[type] == NULL);

                data->clusters[type] = cluster2generate;
            }
        }
        else
        {
            MESSAGE("RxPipelineNodePS2MatInstanceGenerateCluster; this cluster does not have attributes in the PS2 attributes set");
        }
    }
    else
    {
        RWERROR((E_RW_NULLP));
    }

    RWRETURN(result);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2MatInstanceSetVUBufferSizes is used to
 * set the maximum number of triangles (for trilists) and vertices (for
 * tristrips) that may be passed to the VU code section of the pipe
 * containing this node. The stride (in quad words) of the input buffer in
 * vector memory is also set by this function. If this function is not
 * called the defaults required by the standard transforms will be used.
 * For pipelines feeding user supplied VU code, these values may be obtained
 * from the pipeline specific header file created by processing stddata.i
 *
 * Note that the buffer sizes submitted may be rounded down. For trilists,
 * buffer sizes will be rounded to a multiple of four triangles (or twelve
 * vertices) due to DMA read alignment requirements. For tristrips, if your
 * vertices contain any broken-out clusters (that is flagged with CL_ATTRIB_READ
 * or CL_ATTRIB_WRITE in their attributes) then the buffer size will be rounded
 * down to (4n + 2) and if there are no broken-out clusters then (4n). This
 * is due again to DMA read alignment requirements and also internal vertex
 * duplication used to continue tristrips between batches.
 *
 * This function must be called before the pipeline containing this node
 * is unlocked.
 *
 * \param  self     A pointer to a PS2MatInstance.csl \ref RxPipelineNode
 * \param  strideOfInputCluster   stride of cluster in VU memory
 * \param  vuTSVertexMaxCount   maximum number of vertices per TriStrip batch
 * \param  vuTLTriMaxCount   maximum number of triangles per TriList batch
 *
 * \return a pointer to this node on success, otherwise NULL
 *
 * \see RxPipelineNodePS2MatInstanceSetPointListVUBufferSize
 * \see RxPipelineNodePS2MatInstanceGenerateCluster
 * \see RxPipelineNodePS2MatBridgeSetVIFOffset
 * \see RxPipelineNodePS2MatBridgeSetVU1CodeArray
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2MatInstanceSetVUBufferSizes(RxPipelineNode *self, 
                                                 RwInt32 strideOfInputCluster, 
                                                 RwInt32 vuTSVertexMaxCount, 
                                                 RwInt32 vuTLTriMaxCount)
{
    rwPS2MatInstanceInitData *data;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2MatInstanceSetVUBufferSizes"));

    RWASSERT( self != NULL );
    RWASSERT(strideOfInputCluster > 0);
    RWASSERT(vuTSVertexMaxCount >= 3);
    RWASSERT(vuTLTriMaxCount > 0);

    data = (rwPS2MatInstanceInitData *)RxPipelineNodeGetInitData(self);
    if ((data != NULL) &&
        (data->pipeType & rpMESHHEADERPOINTLIST))
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2MatInstanceSetVUBufferSizes",
                           "Pipes can currently be either triangle/line pipes or point pipes, not both");
        RWRETURN((RxPipelineNode *)NULL);
    }

    if (data == NULL)
    {
        if (PS2MatInstanceCreateInitData(self) == FALSE)
        {
            RWRETURN((RxPipelineNode *)NULL);
        }
        data = (rwPS2MatInstanceInitData *)RxPipelineNodeGetInitData(self);
    }
    data->vu1MaxTLInputSize = vuTLTriMaxCount*3*strideOfInputCluster;
    data->vu1MaxTSInputSize = vuTSVertexMaxCount*strideOfInputCluster;
    data->checkSizeOnVU     = strideOfInputCluster;
    data->pipeType          = ( (RpMeshHeaderFlags)
                                (rpMESHHEADERTRISTRIP|rpMESHHEADERTRIFAN|
                                 rpMESHHEADERLINELIST|rpMESHHEADERPOLYLINE) );

    RWRETURN(self);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxPipelineNodePS2MatInstanceSetPointListVUBufferSize is used
 * to set the maximum number of vertices for pointlists that may be passed
 * to the VU code section of the pipe containing this node. The stride
 * (in quad words) of the input buffer in vector memory is also set by
 * this function.
 *
 * This function should be called instead of
 * \ref RxPipelineNodePS2MatInstanceSetVUBufferSizes, they should not both be
 * called. Pipelines set up with this function will only be able to handle
 * pointlist objects, not triangle- or line-based objects. For pipelines
 * feeding user supplied VU code, the values to pass to this function may
 * be obtained from the pipeline specific header file created by processing
 * stddata.i. Note that for a pointlist pipeline, the first half of the VU1
 * code array should be used - i.e the TRANSTRI slots are reused.
 * (see \ref RxPipelineNodePS2MatBridgeSetVU1CodeArray)
 *
 * Note that the buffer size submitted may be rounded down. For pointlists,
 * it will be rounded down to (4n) due to DMA read alignment requirements.
 *
 * This function should be called before unlocking the pipeline containing
 * this node.
 *
 * \param  self   A pointer to a PS2MatInstance.csl \ref RxPipelineNode
 * \param  strideOfInputCluster   stride of cluster in VU memory
 * \param  vuPLVertexMaxCount   maximum number of vertices per PointList batch
 *
 * \return a pointer to this node on success, otherwise NULL
 *
 * \see RxPipelineNodePS2MatInstanceSetVUBufferSizes
 * \see RxPipelineNodePS2MatInstanceGenerateCluster
 * \see RxPipelineNodePS2MatBridgeSetVIFOffset
 * \see RxPipelineNodePS2MatBridgeSetVU1CodeArray
 * \see RxNodeDefinitionGetPS2MatInstance
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 */
RxPipelineNode *
RxPipelineNodePS2MatInstanceSetPointListVUBufferSize(
     RxPipelineNode *self, 
     RwInt32 strideOfInputCluster,
     RwInt32 vuPLVertexMaxCount)
{
    rwPS2MatInstanceInitData *data;

    RWAPIFUNCTION(RWSTRING("RxPipelineNodePS2MatInstanceSetPointListVUBufferSize"));

    RWASSERT( self != NULL );
    RWASSERT(strideOfInputCluster > 0);
    RWASSERT(vuPLVertexMaxCount > 0);

    data = (rwPS2MatInstanceInitData *)RxPipelineNodeGetInitData(self);
    if ((data != NULL) &&
        (data->pipeType &
         (rpMESHHEADERTRISTRIP|rpMESHHEADERTRIFAN|
          rpMESHHEADERLINELIST|rpMESHHEADERPOLYLINE)))
    {
        RwDebugSendMessage(rwDEBUGMESSAGE,
                           "RxPipelineNodePS2MatInstanceSetVUPointListBufferSize",
                           "Pipes can currently be either triangle/line pipes or point pipes, not both");
        RWRETURN((RxPipelineNode *)NULL);
    }

    if (data == NULL)
    {
        if (PS2MatInstanceCreateInitData(self) == FALSE)
        {
            RWRETURN((RxPipelineNode *)NULL);
        }
        data = (rwPS2MatInstanceInitData *)RxPipelineNodeGetInitData(self);
    }

    data->vu1MaxPLInputSize = vuPLVertexMaxCount*strideOfInputCluster;
    data->checkSizeOnVU     = strideOfInputCluster;
    data->pipeType          = rpMESHHEADERPOINTLIST;

    RWRETURN(self);
}

/**
 * \ingroup rpworldp2sky2
 * \ref RxNodeDefinitionGetPS2MatInstance returns a pointer to a
 * node to "instance" a mesh. Note that this node has a construction
 * time API.
 * 
 * On the PS2, instancing of data is performed in the material pipeline
 * on a per-mesh basis. This is because different material pipelines
 * can require data to be instanced into a different format, depending
 * on what the nodes in the pipeline and VU1 code (associated with the
 * pipeline) require. This node can instance meshes containing any
 * \ref RwPrimitiveType, indexed or unindexed.
 *
 * For every data type required to be "broken out" (vertex positions,
 * normals, prelight colours, UVs or user-defined data types), a cluster
 * is created (see the documentation for
 * \ref RxPipelineNodePS2MatInstanceGenerateCluster - briefly, it is requested
 * by the application at pipeline construction time that these data types are
 * exposed in clusters to the subsequent nodes in the CPU-side material
 * pipeline. The default is for data to be invisible, since the optimal format
 * for instanced data is a complex, packed DMA-readable structure which is
 * not suitable for referencing as cluster data).
 *
 * The node checks which parts of the mesh's data need reinstancing. It
 * instances this (from the source world sector or atomic) into the
 * DMA-readable structure in a \ref RwResEntry which is referenced by the
 * RxClPS2Mesh cluster. Every data type exposed by a cluster is instanced
 * into a suitable form (again see the documentation for
 * \ref RxPipelineNodePS2MatInstanceGenerateCluster). Depending on how much
 * has changed in the mesh (from modified UVs to a completely rebuilt mesh)
 * only a subset of the data may be reinstanced or (more costly) the entire
 * \ref RwResEntry may be thrown away and the DMA structure rebuilt from
 * scratch.
 * 
 * \verbatim
   The node has one output, through which the instanced mesh passes.
   The input requirements of this node:
  
   RxClPS2DMASessionRecord  - required
   RxClPS2Mesh              - required
  
   The characteristics of this node's first output:
  
   RxClPS2DMASessionRecord  - valid
   RxClPS2Mesh              - valid
   \endverbatim
 *
 * \return pointer to a node to "instance" a mesh
 *
 * \see RxPipelineNodePS2MatInstanceSetVUBufferSizes
 * \see RxPipelineNodePS2MatInstanceSetPointListVUBufferSize
 * \see RxPipelineNodePS2MatInstanceGenerateCluster
 * \see RxPipelineNodePS2MatBridgeSetVIFOffset
 * \see RxPipelineNodePS2MatBridgeSetVU1CodeArray
 * \see RxNodeDefinitionGetPS2ObjAllInOne
 * \see RxNodeDefinitionGetPS2MatBridge
 */
RxNodeDefinition *
RxNodeDefinitionGetPS2MatInstance(void)
{
    /*********************************************/
    /**                                         **/
    /**  PS2MATINSTANCE.CSL NODE SPECIFICATION  **/
    /**                                         **/
    /*********************************************/


    /* input requirements (this array parallel to ClusterRefs) */
    static RxClusterValidityReq nodeReqs[NUMCLUSTERSOFINTEREST] =
    {
        rxCLREQ_REQUIRED              ,   rxCLREQ_REQUIRED
    };

    /* output state (this array parallel to ClusterRefs) */
    static RxClusterValid nodeOut1[NUMCLUSTERSOFINTEREST] =
    {
        rxCLVALID_VALID               ,   rxCLVALID_VALID
    };

    static RwChar _DefaultOutput[] = RWSTRING("DefaultOutput");

    static RxOutputSpec nodeOuts[] =
    {
        {
            _DefaultOutput,              /* Name */
            nodeOut1,                    /* OutputClusters */
            rxCLVALID_NOCHANGE           /* AllOtherClusters */
        }
    };

#define NUMOUTPUTS \
        ((sizeof(nodeOuts))/(sizeof(nodeOuts[0])))

    static RwChar _PS2MatInstance_csl[] = RWSTRING("PS2MatInstance.csl");

    static RxNodeDefinition nodePS2MatInstanceCSL =
    {
        _PS2MatInstance_csl,                   /* Name */
        {                                      /* nodemethods */
            PS2MatInstanceNodeBody,         /* +-- nodebody */
            (RxNodeInitFn)NULL,                /* +-- nodeinit */
            (RxNodeTermFn)NULL,                /* +-- nodeterm */
            _rwPS2MatInstancePipelineNodeInit, /* +-- pipelinenodeinit */
            (RxPipelineNodeTermFn) NULL,       /* +-- pipelineNodeTerm */
            (RxPipelineNodeConfigFn) NULL,     /* +-- pipelineNodeConfig */
            (RxConfigMsgHandlerFn) NULL        /* +-- configMsgHandler */

        },
        {                                      /* Io */
            NUMCLUSTERSOFINTEREST,             /* +-- NumClustersOfInterest */
            nodeClusters,                      /* +-- ClustersOfInterest */
            nodeReqs,                          /* +-- InputRequirements */
            NUMOUTPUTS,                        /* +-- NumOutputs */
            nodeOuts                           /* +-- Outputs */
        },
        sizeof(PRIVATEDATATYPE),               /* PipelineNodePrivateDataSize */
        rxNODEDEFCONST,                        /* editable */
        (RwInt32)0                             /* inPipes */
    };

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

    RxNodeDefinition *result = &nodePS2MatInstanceCSL;

    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetPS2MatInstance"));

    /*RWMESSAGE((RWSTRING("Pipeline II node")));*/

    RWRETURN(result);
}

