
/* *INDENT-OFF* */

/*
 * ps2allim3d
 * default Im3D-specific callback functions for PS2-specific render pipelines
 * 
 * Copyright (c) Criterion Software Limited
 */

/****************************************************************************
 *                                                                          *
 * module : ps2allim3d.c                                                    *
 *                                                                          *
 * purpose: Im3D-specific PS2All callbacks                                  *
 *                                                                          *
 ****************************************************************************/

/*
#### SYNCHRONISATION
####
#### UP TO DATE WITH VERSION 1.77 OF nodePS2Manager.c
#### UP TO DATE WITH VERSION 1.85 OF nodePS2ObjAllInOne.c
#### UP TO DATE WITH VERSION 1.174 OF nodePS2MatInstance.c
#### UP TO DATE WITH VERSION 1.113 OF nodePS2MatBridge.c
####
#### SYNCHRONISATION
*/

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

#include "matputil.h"

#include "ps2alldbg.h"
#include "nodePS2All.h"
#include "matinstance.h"
#include "matbridge.h"
#include "ps2allatomic.h"

#include "ps2allim3d.h"


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

//TODO[7]: TEMPORARY GLOBAL VARIABLES FOR THE IM3D SPLITTING STUFF
//TODO[3]: THIS IS TOTALLY UN-THREADSAFE, BUT IS IT *REALLY* DANGEROUS?
RwMeshCache    gIm3DMeshCache;
RpMeshHeader   gIm3DMeshHeader[2];
im3DPS2AllData gIm3DData;


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

   Functions

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

/********************* Wrapper funcs for macros in debug **********************/

/**
 * \ingroup rwim3dps2all
 * \ref RwIm3DPS2AllGetMeshHeaderMeshCache is a macro
 * to set up an RpMeshHeader and RwMeshCache for RwIm3D
 * rendering.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RwIm3D,
 * \ref RwIm3DPS2AllObjectSetupCallBack. It fills in the
 * meshHeader and meshCache fields of the \ref RxPS2AllPipeData
 * struct, using a fake, RpMaterial, RwMeshCache and RpMeshHeader
 * to do so (since there are no such structures in RwIm3D
 * rendering). It may be used in constructing user callbacks.
 *
 * This macro will be a function in a debug build, but it may be
 * used explicitly in macro form through the name
 * RpWorldSectorPS2AllGetMeshHeaderMeshCacheMacro and in function
 * form through RpWorldSectorPS2AllGetMeshHeaderMeshCacheFunc,
 * depending on how you wish to balance code size and function
 * call overheads. Note that the macro contains RWASSERT, so it
 * must be used within functions using RWFUNCTION and RWRETURN.
 *
 *
 * \param  stash          A pointer to the current rwIm3DPoolStash
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \see RwIm3DPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
#if (defined(DOXYGEN))
RwIm3DPS2AllGetMeshHeaderMeshCache(rwIm3DPoolStash *stash,
                                   RxPS2AllPipeData *ps2AllPipeData)
#else /* (defined(DOXYGEN)) */
RwIm3DPS2AllGetMeshHeaderMeshCacheFunc(rwIm3DPoolStash *stash,
                                       RxPS2AllPipeData *ps2AllPipeData)
#endif /* (defined(DOXYGEN)) */
{
    RWFUNCTION(RWSTRING("RwIm3DPS2AllGetMeshHeaderMeshCache"));
    RwIm3DPS2AllGetMeshHeaderMeshCacheMacro(stash, ps2AllPipeData);
    RWRETURNVOID();
}

/**
 * \ingroup rwim3dps2all
 * \ref RpWorldSectorPS2AllGatherObjMetrics is a macro
 * to gather metrics information for RwIm3D rendering.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RwIm3D,
 * \ref RwIm3DPS2AllObjectSetupCallBack. It gathers metrics
 * information from the current rwIm3DPoolStash (though in
 * non-RWMETRICS builds, the macro will boil away to nothing
 * during compilation). It may be used in constructing user
 * callbacks.
 *
 * This macro will be a function in a debug build, but it
 * may be used explicitly in macro form through the name
 * RwIm3DPS2AllGatherObjMetricsMacro and in function
 * form through RwIm3DPS2AllGatherObjMetricsFunc,
 * depending on how you wish to balance code size and
 * function call overheads. Note that the macro contains
 * RWASSERT, so it must be used within functions using
 * RWFUNCTION and RWRETURN.
 *
 *
 * \param  stash          A pointer to the current rwIm3DPoolStash
 *
 * \see RwIm3DPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
#if (defined(DOXYGEN))
RwIm3DPS2AllGatherObjMetrics(rwIm3DPoolStash *stash)
#else /* (defined(DOXYGEN)) */
RwIm3DPS2AllGatherObjMetricsFunc(rwIm3DPoolStash *stash)
#endif /* (defined(DOXYGEN)) */
{
    RWFUNCTION(RWSTRING("RwIm3DPS2AllGatherObjMetrics"));
    RwIm3DPS2AllGatherObjMetricsMacro(stash);
    RWRETURNVOID();
}

/**
 * \ingroup rwim3dps2all
 * \ref RwIm3DPS2AllObjInstanceTest is a macro to ensure
 * that full instancing occurs during RwIm3D rendering.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RwIm3D,
 * \ref RwIm3DPS2AllObjectSetupCallBack. It simply sets the
 * objInstance identifier of the \ref RxPS2AllPipeData
 * struct to rxINSTANCEFULLINSTANCE, since full instancing
 * must occur for every RwIm3D render. It may be used in
 * constructing user callbacks.
 *
 * This macro will be a function in a debug build, but it
 * may be used explicitly in macro form through the name
 * RwIm3DPS2AllObjInstanceTestMacro and in function
 * form through RwIm3DPS2AllObjInstanceTestFunc,
 * depending on how you wish to balance code size and
 * function call overheads. Note that the macro contains
 * RWASSERT, so it must be used within functions using
 * RWFUNCTION and RWRETURN.
 *
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \see RwIm3DPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
#if (defined(DOXYGEN))
RwIm3DPS2AllObjInstanceTest(RxPS2AllPipeData *ps2AllPipeData)
#else /* (defined(DOXYGEN)) */
RwIm3DPS2AllObjInstanceTestFunc(RxPS2AllPipeData *ps2AllPipeData)
#endif /* (defined(DOXYGEN)) */
{
    RWFUNCTION(RWSTRING("RwIm3DPS2AllObjInstanceTest"));
    RwIm3DPS2AllObjInstanceTestMacro(ps2AllPipeData);
    RWRETURNVOID();
}

/**
 * \ingroup rwim3dps2all
 * \ref RwIm3DPS2AllTransformSetup is a macro to set up the
 * transformation matrix for RwIm3D rendering.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RwIm3D,
 * \ref RwIm3DPS2AllObjectSetupCallBack. It concatenates
 * the matrix passed to \ref RwIm3DTransform (if none was
 * passed, it uses the identity matrix) with the current
 * camera's view matrix (see \ref RwCameraGetViewMatrix)
 * and stores the result in the matrix passed in. It may
 * be used in constructing user callbacks.
 *
 * This macro will be a function in a debug build, but it
 * may be used explicitly in macro form through the name
 * RwIm3DPS2AllTransformSetupMacro and in function
 * form through RwIm3DPS2AllTransformSetupFunc,
 * depending on how you wish to balance code size and
 * function call overheads. Note that the macro contains
 * RWASSERT, so it must be used within functions using
 * RWFUNCTION and RWRETURN.
 *
 *
 * \param  stash       A pointer to the current rwIm3DPoolStash
 * \param  transform   A pointer to a pointer to the \ref RwMatrix
 *                     holding the object-space to camera-space
 *                     transform for the current RpWorldSector
 *
 * \see RwIm3DPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
#if (defined(DOXYGEN))
RwIm3DPS2AllTransformSetup(rwIm3DPoolStash *stash,
                           RwMatrix **transform)
#else /* (defined(DOXYGEN)) */
RwIm3DPS2AllTransformSetupFunc(rwIm3DPoolStash *stash,
                               RwMatrix **transform)
#endif /* (defined(DOXYGEN)) */
{
    RWFUNCTION(RWSTRING("RwIm3DPS2AllTransformSetup"));
    RwIm3DPS2AllTransformSetupMacro(stash, transform);
    RWRETURNVOID();
}

/**
 * \ingroup rwim3dps2all
 * \ref RwIm3DPS2AllFrustumTest is a macro to test
 * RwIm3D geometry against the current camera's view frustum
 * to determine if clipping is necessary.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RwIm3D,
 * \ref RwIm3DPS2AllObjectSetupCallBack. It simply checks
 * whether the flags passed to \ref RwIm3DTransform contained
 * rwIM3D_NOCLIP or not. It may be used in constructing user
 * callbacks.
 *
 * This macro will be a function in a debug build, but it
 * may be used explicitly in macro form through the name
 * RwIm3DPS2AllFrustumTestMacro and in function
 * form through RwIm3DPS2AllFrustumTestFunc,
 * depending on how you wish to balance code size and
 * function call overheads. Note that the macro contains
 * RWASSERT, so it must be used within functions using
 * RWFUNCTION and RWRETURN.
 *
 *
 * \param  stash          A pointer to the current rwIm3DPoolStash
 * \param  inFrustum      A pointer to a \ref RwFrustumTestResult
 *                        to receive the result of a frustum test
 *                        on the RwIm3D geometry
 *
 * \see RwIm3DPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
#if (defined(DOXYGEN))
RwIm3DPS2AllFrustumTest(rwIm3DPoolStash *stash,
                        RwFrustumTestResult *inFrustum)
#else /* (defined(DOXYGEN)) */
RwIm3DPS2AllFrustumTestFunc(rwIm3DPoolStash *stash,
                            RwFrustumTestResult *inFrustum)
#endif /* (defined(DOXYGEN)) */
{
    RWFUNCTION(RWSTRING("RwIm3DPS2AllFrustumTest"));
    RwIm3DPS2AllFrustumTestMacro(stash, inFrustum);
    RWRETURNVOID();
}

/**
 * \ingroup rwim3dps2all
 * \ref RwIm3DPS2AllResEntryAlloc is a macro to allocate
 * space for the instance data of RwIm3D rendering.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllMatResEntryAllocCallBack for
 * RwIm3D, \ref RwIm3DPS2AllResEntryAllocCallBack. It
 * allocates the space required using an internal
 * circular allocation buffer (appropriate for RwIm3D
 * because we know the memory will be freed, after one
 * use, in ther order in which it is allocated - currently,
 * the maximum allocation size using this scheme is 64KB,
 * which is what causes the 'splitting' described in
 * \ref RwIm3DSkyGetPS2AllMatPipeline). It may be used
 * in constructing user callbacks.
 *
 * This macro will be a function in a debug build, but it
 * may be used explicitly in macro form through the name
 * RwIm3DPS2AllResEntryAllocMacro and in function form
 * through RwIm3DPS2AllResEntryAllocFunc, depending on
 * how you wish to balance code size and function call
 * overheads. Note that the macro contains RWASSERT,
 * so it must be used within functions using RWFUNCTION
 * and RWRETURN.
 *
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 * \param  repEntry       A pointer to a pointer to a \ref RwResEntry
 * \param  size           A \ref RwUInt32 value specifying the size
 *                        in bytes of the memory block to allocate
 *                        (excluding the \ref RwResEntry header).
 * \param  destroyNotify  A \ref RwResEntryDestroyNotify callback
 *                        (see \ref RwResourcesFreeResEntry)
 *
 * \see RwIm3DPS2AllResEntryAllocCallBack
 * \see RxPipelineNodePS2AllMatResEntryAllocCallBack
 */
void
#if (defined(DOXYGEN))
RwIm3DPS2AllResEntryAlloc(RxPS2AllPipeData *ps2AllPipeData,
                          RwResEntry **repEntry,
                          RwUInt32 size,
                          RwResEntryDestroyNotify destroyCallBack)
#else /* (defined(DOXYGEN)) */
RwIm3DPS2AllResEntryAllocFunc(RxPS2AllPipeData *ps2AllPipeData,
                              RwResEntry **repEntry,
                              RwUInt32 size,
                              RwResEntryDestroyNotify destroyCallBack)
#endif /* (defined(DOXYGEN)) */
{
    RWFUNCTION(RWSTRING("RwIm3DPS2AllResEntryAlloc"));
    RwIm3DPS2AllResEntryAllocMacro(
        ps2AllPipeData, repEntry, size, destroyCallBack);
    RWRETURNVOID();
}

/**
 * \ingroup rwim3dps2all
 * \ref RwIm3DPS2AllGatherMeshMetrics is a macro to
 * gather metrics information on RwIm3D rendering.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllMatPostMeshCallBack for RwIm3D,
 * \ref RwIm3DPS2AllPostMeshCallBack. It gathers metrics
 * information on the current RwIm3D render (though in
 * non-RWMETRICS builds, the macro will boil away to nothing
 * during compilation). Note that it currently only counts
 * triangles, not other primitives. It may be used in
 * constructing user callbacks.
 *
 * This macro will be a function in a debug build, but it
 * may be used explicitly in macro form through the name
 * RwIm3DPS2AllGatherMeshMetricsMacro and in function form
 * through RwIm3DPS2AllGatherMeshMetricsFunc, depending on
 * how you wish to balance code size and function call
 * overheads. Note that the macro contains RWASSERT, so
 * it must be used within functions using RWFUNCTION and
 * RWRETURN.
 *
 *
 * \param  stash          A pointer to the current rwIm3DPoolStash
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \see RwIm3DPS2AllPostMeshCallBack
 * \see RxPipelineNodePS2AllMatPostMeshCallBack
 */
void
#if (defined(DOXYGEN))
RwIm3DPS2AllGatherMeshMetrics(rwIm3DPoolStash *stash,
                              RxPS2AllPipeData *ps2AllPipeData)
#else /* (defined(DOXYGEN)) */
RwIm3DPS2AllGatherMeshMetricsFunc(rwIm3DPoolStash *stash,
                                  RxPS2AllPipeData *ps2AllPipeData)
#endif /* (defined(DOXYGEN)) */
{
    RWFUNCTION(RWSTRING("RwIm3DPS2AllGatherMeshMetrics"));
    RwIm3DPS2AllGatherMeshMetricsMacro(_stch, ps2AllPipeData);
    RWRETURNVOID();
}

/* Splitting explained in RwIm3DSkyGetPS2AllMatPipeline docs in fastim3d.c */
void
RwIm3DPS2AllMeshEndFunc(rwIm3DPoolStash *stash,
                        RxPS2AllPipeData *ps2AllPipeData)
{
    RWFUNCTION(RWSTRING("RwIm3DPS2AllMeshEnd"));
    RwIm3DPS2AllMeshEndMacro(stash, ps2AllPipeData);
    RWRETURNVOID();
}

/***************** End of wrapper funcs for macros in debug ******************/


/**
 * \ingroup rwim3dps2all
 * \ref RwIm3DPS2AllObjectSetupCallBack is the
 * \ref RxPipelineNodePS2AllObjectSetupCallBack used in the default
 * RenderWare RwIm3D render pipeline.
 *
 * See \ref RxPipelineNodePS2AllObjectSetupCallBack for an overview
 * of this callback type. For RwIm3D, this default callback is
 * composed of the following helper macros (of which you may use
 * some or all to construct your own replacement callback), in the
 * order shown here (try and keep this order, some macros rely on
 * the results of prior ones):
 *
 * \li \ref RwIm3DPS2AllGetMeshHeaderMeshCache, 
 * \li \ref RwIm3DPS2AllGatherObjMetrics,
 * \li \ref RwIm3DPS2AllObjInstanceTest,
 * \li \ref RwIm3DPS2AllTransformSetup,
 * \li \ref RwIm3DPS2AllFrustumTest,
 * \li \ref RpAtomicPS2AllPrimTypeTransTypeSetup,
 *
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 * \param  transform      A pointer to a pointer to a transform matrix
 * \param  lightingFunc   A pointer to a \ref RxWorldApplyLightFunc
 *                        function to upload information about lights
 *                        to VU1
 *
 * \return TRUE on success, FALSE to prematurely terminate the pipeline
 *
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 * \see RwIm3DPS2AllGetMeshHeaderMeshCache
 * \see RwIm3DPS2AllGatherObjMetrics
 * \see RwIm3DPS2AllObjInstanceTest
 * \see RwIm3DPS2AllTransformSetup
 * \see RwIm3DPS2AllFrustumTest
 * \see RpAtomicPS2AllPrimTypeTransTypeSetup
 */
RwBool
RwIm3DPS2AllObjectSetupCallBack(
    RxPS2AllPipeData *ps2AllPipeData,
    RwMatrix **transform,
    RxWorldApplyLightFunc lightingFunc)
{
    rwIm3DPoolStash    *stash;
    RwFrustumTestResult inFrustum;

    RWFUNCTION(RWSTRING("RwIm3DPS2AllObjectSetupCallBack"));

    stash = (rwIm3DPoolStash *)(ps2AllPipeData->sourceObject);
    RWASSERT(NULL != stash);

    /* For Im3D (to avoid a malloc), the meshCache/meshHeader are statically allocated. */
    RwIm3DPS2AllGetMeshHeaderMeshCache(stash, ps2AllPipeData);

    /* Gather metrics */
    RwIm3DPS2AllGatherObjMetrics(stash);

//TODO[3]: EARLY OUT IF....?
#if 0
    if(???)
    {
        /* Oy... we have geometry with vertices but no triangles
         * We have to test because with plugin data to compensate
         * (e.g bezier control points, or procedural vector code
         * ala particles), it might be valid... :o/ */
        RWRETURN(TRUE);
    }
#endif

    /* We always do a full reinstance for Im3D */
    RwIm3DPS2AllObjInstanceTest(ps2AllPipeData);

    /* We need to cache the transform */
    RwIm3DPS2AllTransformSetup(stash, transform);

    /* Set inFrustum from the stash hint flag */
    RwIm3DPS2AllFrustumTest(stash, &inFrustum);

    /* Set up primType and transtype (share the atomic version).
     * Use the converted meshheader flags for primtype testing. */
    RpAtomicPS2AllPrimTypeTransTypeSetup(ps2AllPipeData, inFrustum);

    /* We don't have a material in Im3D, keep the default FALSE value */

    /* We don't do lighting in Im3D by default either, though users could do so. */

    RWRETURN(TRUE);
}


/* We don't need a "RwIm3DPS2AllMeshInstanceTestCallBack", the objectsetupCB
 * can quite well decide that we always reinstance for Im3d - besides,
 * there's only one mesh in im3d :o) */


/**
 * \ingroup rwim3dps2all
 * \ref RwIm3DPS2AllResEntryAllocCallBack is the
 * \ref RxPipelineNodePS2AllMatResEntryAllocCallBack used in the
 * default RenderWare RwIm3D render pipeline.
 *
 * See \ref RxPipelineNodePS2AllObjectFinalizeCallBack for an overview
 * of this callback type. For RwIm3D, this default callback is
 * composed of the following helper macro (which you may use
 * in constructing your own replacement callback):
 *
 * \ref RwIm3DPS2AllResEntryAlloc
 *
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \return TRUE on success, FALSE otherwise
 *
 * \see RxPipelineNodePS2AllMatResEntryAllocCallBack
 * \see RwIm3DPS2AllResEntryAlloc
 */
RwResEntry *
RwIm3DPS2AllResEntryAllocCallBack(RxPS2AllPipeData *ps2AllPipeData,
                                  RwResEntry **repEntry,
                                  RwUInt32 size,
                                  RwResEntryDestroyNotify destroyCallBack)
{
    RWFUNCTION(RWSTRING("RwIm3DPS2AllResEntryAllocCallBack"));

    RwIm3DPS2AllResEntryAlloc(ps2AllPipeData, repEntry, size, destroyCallBack);

    if (NULL == *repEntry)
    {
        REDEBUGPrintf(("Im3D DMA memory couldn't be alloc'd. Exiting...\n\n"));
        RWRETURN(NULL);
    }

    RWRETURN(*repEntry);
}

/**
 * \ingroup rwim3dps2all
 * \ref RwIm3DPS2AllInstanceCallBack is the
 * \ref RxPipelineNodePS2AllMatInstanceCallBack used in the default
 * RenderWare RwIm3D render pipeline.
 *
 * See \ref RxPipelineNodePS2AllObjectFinalizeCallBack for an overview
 * of this callback type. For RwIm3D, this default callback just calls
 * RwIm3DPS2AllInstance, since no user instancing is performed.
 *
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
*/

/*
 * \return TRUE on success, FALSE otherwise
 *
 * \see RxPipelineNodePS2AllMatInstanceCallBack
 */
RwBool
RwIm3DPS2AllInstanceCallBack(RxPS2AllPipeData *ps2AllPipeData,
                             void ** __RWUNUSED__ clusters,
                             RwUInt32 __RWUNUSED__ numClusters)
{
    RWFUNCTION(RWSTRING("RwIm3DPS2AllInstanceCallBack"));

    REDEBUGPrintf(("parent is an Im3D primitive\n"));

    RWASSERT(NULL != ps2AllPipeData);
    RWRETURN(RwIm3DPS2AllInstance(ps2AllPipeData));

    /* A user callback would instance user clusters here */
}

/**
 * \ingroup rwim3dps2all
 * \ref RwIm3DPS2AllBridgeCallBack is the
 * \ref RxPipelineNodePS2AllMatBridgeCallBack used in the
 * default RenderWare RwIm3D render pipeline.
 *
 * See \ref RxPipelineNodePS2AllMatBridgeCallBack for an overview
 * of this callback type. For RwIm3D, this default callback is
 * composed of the following helper macros (of which you may use
 * either or both in constructing your own replacement callback),
 * in the order shown here (you should keep the same order, as
 * explained in \ref RpMeshPS2AllStartVIFUploads):
 *
 * \ref RpMeshPS2AllStartVIFUploads,
 * \ref RpMeshPS2AllGIFTagUpload,
 * \ref RpMeshPS2AllMatColUpload,
 * \ref RpMeshPS2AllSurfPropsUpload,
 * \ref RpMeshPS2AllClipInfoUpload,
 * \ref RpMeshPS2AllVU1CodeIndexSetup,
 * \ref RpMeshPS2AllVU1CodeUpload,
 * \ref RpMeshPS2AllEndVIFUploads
 *
 * Note that the only difference between this list and that of
 * \ref RpMeshPS2AllBridgeCallBack is the omission here of any
 * texture setup. All renderstate persists through RwIm3D render
 * calls, whereas RpAtomic or RpWorldSector render calls will
 * change texture renderstate dependent on materials (RwIm3D
 * does not use materials).
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \return TRUE on success, FALSE otherwise
 *
 * \see RxPipelineNodePS2AllMatMeshInstanceTestCallBack
 * \see RpMeshPS2AllStartVIFUploads,
 * \see RpMeshPS2AllGIFTagUpload,
 * \see RpMeshPS2AllMatColUpload,
 * \see RpMeshPS2AllSurfPropsUpload,
 * \see RpMeshPS2AllClipInfoUpload,
 * \see RpMeshPS2AllVU1CodeIndexSetup,
 * \see RpMeshPS2AllVU1CodeUpload,
 * \see RpMeshPS2AllEndVIFUploads
 */
RwBool
RwIm3DPS2AllBridgeCallBack(RxPS2AllPipeData *ps2AllPipeData)
{
    RwUInt32 numStandardQW;

    RWFUNCTION(RWSTRING("RwIm3DPS2AllBridgeCallBack"));

    /* The Im3D bridge callback does no texture upload/setup,
     * since renderstate persists through Im3D calls. */


    /* If you exit here, the mesh won't be rendered */

    /* Open a VIF transfer packet, with a header DMA tag */
    numStandardQW = rpMESHPS2ALLGIFTAGNUMQW +
                    rpMESHPS2ALLMATCOLNUMQW +
                    rpMESHPS2ALLSURFPROPSNUMQW +
                    rpMESHPS2ALLCLIPINFONUMQW;
    RpMeshPS2AllStartVIFUploads(TRUE, numStandardQW);


    /* Extra user VIF uploads would go here... VIF tag(s) necessary.
     * Would have called _rxPS2AllStartVIFUploads with FALSE if needed
     * DMA tags in the user uploads (would have had to fix up with a
     * terminal DMA tag so the following standard transfers work). */

    /* Here follow the standard VIF uploads */

    /* Upload a GIF tag for the code to submit geom to the GS under */
    RpMeshPS2AllGIFTagUpload(ps2AllPipeData);

    /* Upload material colour, dependent on whether there's a texture
     * (also does some renderstate setup based on alpha - that's why
     * this needs to be before the texture state setup func) */
    RpMeshPS2AllMatColUpload(ps2AllPipeData);

    /* Upload surface properties, including the 'extra' float in W */
    RpMeshPS2AllSurfPropsUpload(ps2AllPipeData);

    /* Sets up clipping info and J-C's switch QWs (NOTE: uses "transType&7") */
    RpMeshPS2AllClipInfoUpload(ps2AllPipeData);

    /* Set up vu1CodeIndex */
    RpMeshPS2AllVU1CodeIndexSetup(ps2AllPipeData);
    /* Upload the VU1 code (if it changed) last, since it uses a DMA tag (ref transfer of the code) */
    RpMeshPS2AllVU1CodeUpload(ps2AllPipeData);

    /* Kicks off geometry transfer, sets up refCnt/clrCnt and
     * closes the packet (so it can be extended later I think) */
    RpMeshPS2AllEndVIFUploads(ps2AllPipeData);

    RWRETURN(TRUE);
}


/****************************************************************************
 RwIm3DPS2AllIm3DPreMeshCallBack
 */
RwBool
RwIm3DPS2AllIm3DPreMeshCallBack(RxPS2AllPipeData *ps2AllPipeData)
{
    RwUInt32                dmaTagSize = 16;
    RwUInt32                alignSize = 16;

    rxNodePS2AllPvtData    *objPvtData;
    rxNodePS2AllMatPvtData *matPvtData;
    rwIm3DPoolStash        *stash;
    RwMeshCache            *meshCache;
    RpMeshHeader           *meshHeader;
    RwPrimitiveType         primType;
    RwBool                  triStrip;

    RwUInt32                numVerts;
    RwUInt32                numBatches;
    RwUInt32                batchSize;
    RwUInt32                skipSize;
    RwUInt32                batchByteSize;
    RwUInt32                headerSize;

    RwUInt32                numClusters;
    RwUInt32                numOpaqueClusters;
    RwUInt32                numNonOpaqueClusters;
    RwUInt32                sumClusterWidth;
    RwUInt32                maxBatchesPerBlock;

    /* We modify mesh members (hence non-const), mesh-splitting is all a big hack that must die */
    RpMesh                 *mesh;
    RwUInt8                *vertToIndRatio;
    RwUInt8                *vertToIndOffset;
    RwUInt32                i;

    RWFUNCTION(RWSTRING("RwIm3DPS2AllIm3DPreMeshCallBack"));


//TODO[7]: will mostly (all?) die when mesh splitting stops

    RWASSERT(NULL != ps2AllPipeData); // redundant given it's used in initialisation above aready...
    objPvtData = ps2AllPipeData->objPvtData;
    RWASSERT(NULL != objPvtData);
    matPvtData = ps2AllPipeData->matPvtData;
    RWASSERT(NULL != matPvtData);

    stash = (rwIm3DPoolStash *)(ps2AllPipeData->sourceObject);
    RWASSERT(NULL != stash);

    meshHeader = ps2AllPipeData->meshHeader;
    RWASSERT(NULL != meshHeader);
    mesh = (RpMesh *)ps2AllPipeData->mesh;
    RWASSERT(NULL != mesh);
    meshCache = ps2AllPipeData->meshCache;
    RWASSERT(NULL != meshCache);
    primType = stash->primType;

    triStrip = (primType == rwPRIMTYPETRISTRIP) ;
    gIm3DData.triFan = (primType == rwPRIMTYPETRIFAN);
    gIm3DData.firstTriFanIndex = (RwImVertexIndex)0;
    gIm3DData.swappedTriFanIndex = (RwImVertexIndex)0;

    vertToIndRatio = &(objPvtData->primLUT.vertToIndRatio[0]);
    vertToIndOffset = &(objPvtData->primLUT.vertToIndOffset[0]);


    /* We split the primitive into batches if it's too big
     * (we use the circular allocator which can only allocate
     * blocks up to 64k in size) */

    numVerts = stash->numIndices;
    if (FALSE != gIm3DData.triFan)
    {
        numVerts = (numVerts - 2) * 3;
    }
    else if (primType == rwPRIMTYPEPOLYLINE)
    {
        numVerts = (numVerts - 1) * 2;
    }

    if (FALSE != triStrip)
    {
        batchSize = matPvtData->triStrip.batchSize;
        numBatches = ((numVerts - 2) + ((batchSize - 2) - 1)) /
            (batchSize - 2);
    }
    else
    {
        batchSize = matPvtData->triList.batchSize;
        numBatches = (numVerts + (batchSize - 1)) / batchSize;
    }

    /* TriFans require extra work - we have to shift the original
     * first vertex/index up to the beginning of each batch. */
    if (FALSE != gIm3DData.triFan)
    {
        if (meshHeader->flags & rpMESHHEADERUNINDEXED)
        {
            gIm3DData.firstTriFanVertex = *(stash->objVerts);
            gIm3DData.swappedTriFanVertex = gIm3DData.firstTriFanVertex;
        }
        else
        {
            gIm3DData.firstTriFanIndex = *(stash->indices);
            gIm3DData.swappedTriFanIndex = gIm3DData.firstTriFanIndex;
        }
    }

    /* For unindexed objects, we will increment the vertex
     * array pointer (PURPOSEFULLY no predication here) */
    gIm3DData.tempObjVerts = stash->objVerts;

    numClusters = 0;
    numOpaqueClusters = 0;
    /* "skip" (in quadwords) is the size of one batch of all the opaque
     * clusters plus all the tags inserted inbetween. If there are no
     * opaque clusters this should be zero. It should be the same for
     * all opaque clusters. */
    skipSize = 0;
    /* Group sizes of non-opaque clusters for simpler calcs later */
    sumClusterWidth = 0;
    for (i = 0; i < CL_MAXCL; i++)
    {
        if (matPvtData->clinfo[i].attrib & CL_ATTRIB_REQUIRED)
        {
            numClusters++;
            if (matPvtData->clinfo[i].attrib & CL_ATTRIB_OPAQUE)
            {
                if (FALSE != triStrip)
                {
                    skipSize =
                        matPvtData->triStrip.fieldRec[i].skip << 4;
                }
                else
                {
                    skipSize =
                        matPvtData->triList.fieldRec[i].skip << 4;
                }
                numOpaqueClusters++;
            }
            else
            {
                /* Non-opaque stride is in WORDs */
                sumClusterWidth += matPvtData->clinfo[i].stride << 2;
            }
        }
    }
    numNonOpaqueClusters = numClusters - numOpaqueClusters;

    if (numOpaqueClusters == 0)
    {
        /* In this case, skip isn't set up because there's no opaque data.
         * It *should* be 2 DMA tags per (non-opaque) cluster:
         * [hmm, seems to overestimate batch size, maybe it's one tag?] */
        skipSize = 2*dmaTagSize*numNonOpaqueClusters;
    }

    /* I don't think we support something with no instanced clusters atm */
    RWASSERT(numClusters > 0);

    /* We assume at least some clusters by reference so we
     * overestimate the contribution of tags to size. Also,
     * people can overload the Im3D pipe and it still has to
     * work coming through here - to avoid excessive work here,
     * we add a conservative alignment padding for all opaque
     * clusters. */
    batchByteSize =
        skipSize +                   /* Size of one opaque batch - all opaque data and the tags
                                      * inbetween (including those for non-opaque clusters) */
        batchSize * sumClusterWidth; /* Size of one batch of non-opaque clusters (data only) */

    /* Round down, don't bother trying to squeeze partial batches in.
     * We want to get this done some time today... */
    headerSize =
        sizeof(RwResEntry) + /* This is factored into the allocated block */
        sizeof(rwPS2AllResEntryHeader) + /* This too */
        dmaTagSize * 2 + /* Maybe there're extra tags at the start/end */
        128 + /* The allocated block is padded by a cache line size */
        numNonOpaqueClusters * alignSize; /* Each broken-out cluster's data is
                                           * padded to QW size */

    maxBatchesPerBlock =
        (sweCircAllocBlockSize - headerSize) / batchByteSize;

    if (maxBatchesPerBlock < numBatches)
    {
        gIm3DData.indOffset =
            maxBatchesPerBlock * (batchSize / vertToIndRatio[primType]);
        if (primType == rwPRIMTYPETRISTRIP)
            gIm3DData.indOffset -= 2 * maxBatchesPerBlock;
        gIm3DData.indPerBlock = gIm3DData.indOffset + vertToIndOffset[primType];
    }
    else
    {
        gIm3DData.indOffset = meshHeader->totalIndicesInMesh;
        gIm3DData.indPerBlock = gIm3DData.indOffset;
    }

    /* The mesh has no particular material (-> use current renderstate) */
    mesh->material = NULL;
    mesh->indices = stash->indices;
    mesh->numIndices = gIm3DData.indPerBlock;

    /* Ensure DMA chain gets built EVERY time round */
    meshCache->meshes[0] = NULL;


    RWRETURN(TRUE);
}


/****************************************************************************
 RwIm3DPS2AllIm3DPostMeshCallBack
 */
RwBool
RwIm3DPS2AllIm3DPostMeshCallBack(RxPS2AllPipeData *ps2AllPipeData)
{
    RpMeshHeader    *meshHeader;
    /* We modify mesh members (hence non-const), mesh-splitting is all a big hack that must die */
    RpMesh          *mesh;
    RwMeshCache     *meshCache;
    rwIm3DPoolStash *stash;

    RWFUNCTION(RWSTRING("RwIm3DPS2AllIm3DPostMeshCallBack"));

//TODO[7]: will mostly die when mesh splitting stops, rest into RwIm3DPS2AllPostMeshCallBack

    RWASSERT(NULL != ps2AllPipeData);
    stash = (rwIm3DPoolStash *)(ps2AllPipeData->sourceObject);
    RWASSERT(NULL != stash);

    meshHeader = ps2AllPipeData->meshHeader;
    RWASSERT(NULL != meshHeader);
    mesh       = (RpMesh *)ps2AllPipeData->mesh;
    RWASSERT(NULL != mesh);
    meshCache = ps2AllPipeData->meshCache;
    RWASSERT(NULL != meshCache);

    /* Undo prior vertex/index swaps for TriFans */
    if (FALSE != gIm3DData.triFan)
    {
        if (NULL != mesh->indices)
        {
            *(mesh->indices) = gIm3DData.swappedTriFanIndex;
        }
        else
        {
            *(stash->objVerts) = gIm3DData.swappedTriFanVertex;
        }
    }

    /* On to the next block of verts if there is one */
    if (gIm3DData.indOffset >= meshHeader->totalIndicesInMesh)
    {
        /* We're finished, yay! */
        meshHeader->totalIndicesInMesh = 0;
    }
    else
    {
        meshHeader->totalIndicesInMesh -= gIm3DData.indOffset;
        if (meshHeader->totalIndicesInMesh <= gIm3DData.indPerBlock)
        {
            /* Last block */
            mesh->numIndices = meshHeader->totalIndicesInMesh;
        }

        /* Increment the index pointer to the next block (if we're
         * unindexed we increment the vertex pointer cos the indices
         * are implicit in the vertex array). For trifans, we have
         * to swap the first index/vert each time round... yawn */
        if (mesh->indices)
        {
            mesh->indices += gIm3DData.indOffset;
            if (FALSE != gIm3DData.triFan)
            {
                gIm3DData.swappedTriFanIndex = *(mesh->indices);
                *(mesh->indices) = gIm3DData.firstTriFanIndex;
            }
        }
        else
        {
            stash->objVerts += gIm3DData.indOffset;
            if (FALSE != gIm3DData.triFan)
            {
                gIm3DData.swappedTriFanVertex = *(stash->objVerts);
                *(stash->objVerts) = gIm3DData.firstTriFanVertex;
            }
        }
    }

    /* This gets the DMA chain memory freed automatically when DMA is
     * done with it (SWE_PKT_NULL -> freed by RwFree() hopefully) */
//TODO[7]: WHEN SPLITTING STOPS, THIS GOES INTO THE POSTMESHCB (IN RwIm3DPS2AllMeshEnd)
//        NOTE: THE NULL SET MUST HAPPEN *AFTER* THIS!! :)
#if (0)
    _sweAddPkt(*(ps2AllPipeData->cacheEntryRef),
               SWE_PKT_NULL | SWE_PKT_LOCAL);
#else /* (0) */
    _sweAddPkt(*(ps2AllPipeData->cacheEntryRef),
               SWE_PKT_NULL | SWE_PKT_CIRCALLOC | SWE_PKT_LOCAL);
#endif /* (0) */

    /* Ensure DMA chain gets built EVERY time round */
    meshCache->meshes[0] = NULL;

    RWRETURN(TRUE);
}


/****************************************************************************
 RwIm3DPS2AllMeshSplitCallBack
 */
RwBool
RwIm3DPS2AllMeshSplitCallBack(RxPS2AllPipeData *ps2AllPipeData)
{
    RWFUNCTION(RWSTRING("RwIm3DPS2AllMeshSplitCallBack"));

//TODO[7]: will die when mesh splitting stops

    RWASSERT(NULL != ps2AllPipeData);
    RWASSERT(NULL != ps2AllPipeData->meshHeader);

    /* Acts as if the following were inserted into PS2AllNodeBody:
     * "while (meshHeader->totalIndicesInMesh > 0)" */

    RWRETURN(ps2AllPipeData->meshHeader->totalIndicesInMesh > 0);
}


/**
 * \ingroup rwim3dps2all
 * \ref RwIm3DPS2AllPostMeshCallBack is the
 * \ref RxPipelineNodePS2AllMatPostMeshCallBack used in the default
 * RenderWare RwIm3D render pipeline.
 *
 * See \ref RxPipelineNodePS2AllMatPostMeshCallBack for an overview
 * of this callback type. For RwIm3D, this default callback (the
 * former is only actually used in RWMETRICS builds) is composed of
 * the following helper macros (of which you may use either or both
 * in constructing your own replacement callback), in the order
 * shown here:
 *
 * \li \ref RwIm3DPS2AllGatherMeshMetrics,
 * \li RwIm3DPS2AllMeshEnd
 *
 * Note that the latter is undocumented. See
 * \ref RwIm3DSkyGetPS2AllMatPipeline for details on why this and
 * other RwIm3D callbacks and helper macros are undocumented.
 *
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \return TRUE on success, FALSE otherwise
 *
 * \see RxPipelineNodePS2AllMatPostMeshCallBack
 * \see RwIm3DPS2AllGatherMeshMetrics
 */
RwBool
RwIm3DPS2AllPostMeshCallBack(RxPS2AllPipeData *ps2AllPipeData
#if (!defined(RWMETRICS))
__RWUNUSED__
#endif /* (!defined(RWMETRICS)) */
                  )
{
    rwIm3DPoolStash *stash;

    RWFUNCTION(RWSTRING("RwIm3DPS2AllPostMeshCallBack"));

    RWASSERT(NULL != ps2AllPipeData);
    stash = (rwIm3DPoolStash *)(ps2AllPipeData->sourceObject);
    RWASSERT(NULL != stash);

    /* Gather per-mesh metrics */
    RwIm3DPS2AllGatherMeshMetrics(stash, ps2AllPipeData);

    /* Tidy up Im3D loose ends */
    RwIm3DPS2AllMeshEnd(stash, ps2AllPipeData);

    RWRETURN(TRUE);
}

/****************************************************************************
 RwIm3DPS2AllInstance()
 */
RwBool
RwIm3DPS2AllInstance(RxPS2AllPipeData *ps2AllPipeData
/*TODO[4]: NEW REINSTANCING STRUCTURE AND DATA BLOCK IN HERE...*/)
{
    rwIm3DPoolStash *stash;
    rwPS2AllResEntryHeader *ps2ResHeader;
    RwUInt32 numVerts;
    RwUInt32 stripTmp = 0;
    /* These next two MUST be signed or they'll wrap around causing infinite loops!! */
    RwInt32 vertCounter, j;
    INDEXDECLARE();

    RWFUNCTION(RWSTRING("RwIm3DPS2AllInstance"));


    RWASSERT(NULL != ps2AllPipeData);
    stash = (rwIm3DPoolStash *)(ps2AllPipeData->sourceObject);
    RWASSERT(NULL != stash);
    ps2ResHeader =
        RWPS2ALLRESENTRYHEADERFROMRESENTRY(*(ps2AllPipeData->cacheEntryRef));
    RWASSERT(NULL != ps2ResHeader);

    numVerts = ps2ResHeader->numVerts;

    REDEBUGPrintf(("RwIm3DPS2AllInstance"));
    REDEBUGPrintf(("numVerts: %d\n", numVerts));
    REDEBUGPrintf(("batchSize: %d\n", ps2ResHeader->batchSize));


    if (ps2AllPipeData->meshHeader->flags & rpMESHHEADERTRISTRIP) stripTmp = 2;
//TODO[3]: ERM... SHOULDN'T STRIPTMP BE A PART OF THE INSTANCING MACROS? HUH? WELL?!?!?!?!
    /* Initialise the indexing LUTs dependant on the
     * primtype and the presence/absence of indices */
    INDEXSETUP(ps2AllPipeData->meshHeader->flags, ps2AllPipeData->mesh->indices);

    /* XYZs are no longer automatic. We could be render private immediate data, eg Rt2d.
     * [also note that we test instancing flags but in almost all cases they'll be true] */
    if ((ps2AllPipeData->meshInstance & rxINSTANCEXYZ) &&
        (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 ((ps2AllPipeData->meshInstance & rxINSTANCEUV) &&
        (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 ((ps2AllPipeData->meshInstance & rxINSTANCERGBA) &&
        (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;
        }
    }

    REDEBUGObjectDMADumpMacro();

    RWRETURN(TRUE);
}

/*TODO[4]: ALLOW LIGHTING IN IM3D? (WHY NOT?) */ 

