
/* *INDENT-OFF* */

/*
 * ps2allatomic
 * default atomic-specific callback functions for PS2-specific object pipelines
 * 
 * Copyright (c) Criterion Software Limited
 */

/****************************************************************************
 *                                                                          *
 * module : ps2allatomic.c                                                  *
 *                                                                          *
 * purpose: Atomic-specific PS2All callbacks                                *
 *                                                                          *
 ****************************************************************************/

/*
#### SYNCHRONISATION
####
#### UP TO DATE WITH VERSION 1.84 OF nodePS2Manager.c
#### UP TO DATE WITH VERSION 1.90 OF nodePS2ObjAllInOne.c
#### UP TO DATE WITH VERSION 1.188 OF nodePS2MatInstance.c
#### UP TO DATE WITH VERSION 1.121 OF nodePS2MatBridge.c
####
#### SYNCHRONISATION
*/

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

#include "matputil.h"

#include "nodeps2all.h"

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

#include "ps2allatomic.h"
#include "ps2allsector.h"

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ = 
    "@@@@(#)$Id: ps2allatomic.c,v 1.45 2001/09/20 17:28:22 iestynb Exp $";
#endif /* (!defined(DOXYGEN)) */

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

   Functions

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

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

#undef RpAtomicPS2AllGetMeshHeaderMeshCache
#define RpAtomicPS2AllGetMeshHeaderMeshCache(_atomic, _ps2AllPipeData) \
        RpAtomicPS2AllGetMeshHeaderMeshCacheFunc(_atomic, _ps2AllPipeData)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllGetMeshHeaderMeshCache is a macro
 * to extract the RpMeshHeader and RwMeshCache from an RpAtomic.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RpAtomics,
 * \ref RpAtomicPS2AllObjectSetupCallBack. It fills in the
 * meshHeader and meshCache fields of the \ref RxPS2AllPipeData
 * struct. 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
 * RpAtomicPS2AllGetMeshHeaderMeshCacheMacro and in function
 * form through RpAtomicPS2AllGetMeshHeaderMeshCacheFunc,
 * depending on how you wish to balance code size and function
 * call overheads.
 *
 *
 * \param  atomic         A pointer to the current RpAtomic
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \see RpAtomicPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
RpAtomicPS2AllGetMeshHeaderMeshCache(RpAtomic *atomic,
                                     RxPS2AllPipeData *ps2AllPipeData)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllGetMeshHeaderMeshCache"));
    RpAtomicPS2AllGetMeshHeaderMeshCacheMacro(atomic, ps2AllPipeData);
    RWRETURNVOID();
}

#undef RpAtomicPS2AllGatherObjMetrics
#define RpAtomicPS2AllGatherObjMetrics(_atomic) \
        RpAtomicPS2AllGatherObjMetricsFunc(_atomic)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllGatherObjMetrics is a macro
 * to gather metrics information from an RpAtomic.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RpAtomics,
 * \ref RpAtomicPS2AllObjectSetupCallBack. It gathers metrics
 * information from the current atomic (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
 * RpWorldSectorPS2AllGatherObjMetricsMacro and in function
 * form through RpWorldSectorPS2AllGatherObjMetricsFunc,
 * depending on how you wish to balance code size and function
 * call overheads.
 *
 *
 * \param  atomic         A pointer to the current RpAtomic
 *
 * \see RpAtomicPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
RpAtomicPS2AllGatherObjMetrics(RpAtomic *atomic __RWUNUSEDUNLESSMETRICS__)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllGatherObjMetrics"));
    RpAtomicPS2AllGatherObjMetricsMacro(atomic);
    RWRETURNVOID();
}

#undef RpAtomicPS2AllMorphSetup
#define RpAtomicPS2AllMorphSetup(_atomic, _ps2AllPipeData) \
        RpAtomicPS2AllMorphSetupFunc(_atomic, _ps2AllPipeData)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllMorphSetup is a macro to set up
 * morphing information for a morph animated RpAtomic.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RpAtomics,
 * \ref RpAtomicPS2AllObjectSetupCallBack. It will fill in
 * the numMorphTargets and spExtra members of the
 * \ref RxPS2AllPipeData struct for morph animated atomics.
 * The spExtra field in such atomics is used to control
 * interpolation between morph targets, which is performed
 * during the transform on VU1. This macro 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
 * RpAtomicPS2AllMorphSetupMacro and in function
 * form through RpAtomicPS2AllMorphSetupFunc,
 * depending on how you wish to balance code size and
 * function call overheads.
 *
 *
 * \param  atomic         A pointer to the current RpAtomic
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \see RpAtomicPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
RpAtomicPS2AllMorphSetup(RpAtomic *atomic,
                         RxPS2AllPipeData *ps2AllPipeData)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllMorphSetup"));
    RpAtomicPS2AllMorphSetupMacro(atomic, ps2AllPipeData);
    RWRETURNVOID();
}

#undef RpAtomicPS2AllObjInstanceTest
#define RpAtomicPS2AllObjInstanceTest(_atomic, _ps2AllPipeData) \
        RpAtomicPS2AllObjInstanceTestFunc(_atomic, _ps2AllPipeData)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllObjInstanceTest is a macro
 * to test an RpAtomic to determine if it needs
 * reinstancing.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RpAtomics,
 * \ref RpAtomicPS2AllObjectSetupCallBack. It generates a new
 * object identifier for the current RpAtomic and stores this
 * in the objIdentifier field of the \ref RxPS2AllPipeData struct.
 * It also tests this identifier against the existing identifier
 * stored in the atomic's instance data (it uses
 * rwMeshCacheGetEntryRef, RWPS2ALLRESENTRYHEADERFROMRESENTRY and
 * RWPS2ALLRESENTRYHEADERGETOBJIDENTIFIER to extract this from the
 * header of the atomic's first mesh's instance data - all meshes
 * within an object will have the same objIdentifier in their
 * instance data header) and updates the objInstance member if
 * it finds any differences.
 *
 * The macro RPATOMICPS2ALLMAKEOBJID can be used to construct
 * the identifier for an RpWorldSector from its RpMeshHeader
 * and RpGeometry. RPATOMICPS2ALLOBJIDGETSERIALNUM can be used
 * to extract the serial number from this indentifier,
 * RPATOMICPS2ALLOBJIDGETFLAGS to extract the geometry flags
 * and RPATOMICPS2ALLOBJIDGETNUMMORPHTARGETS to extract the
 * number of morph targets. All these may be used in
 * constructing user callbacks.
 *
 * A full reinstance (see \ref RxInstanceFlags) will be caused
 * if: the number of morph targets changes, the atomic's
 * RpGeometry was locked for editing using rpGEOMETRYLOCKPOLYGONS
 * or the flags of the atomic's RpGeometry have changed.
 * A congruent reinstance will occur if: the atomic's
 * RpGeometry is locked for editing, the start or end morph
 * targets are changed or any other change is made to the
 * RpGeometry.
 *
 * RpAtomicPS2AllObjInstanceTest will be a function in a
 * debug build, but it may be used explicitly in macro form
 * through the name RpAtomicPS2AllObjInstanceTestMacro and
 * in function form through RpAtomicPS2AllObjInstanceTestFunc,
 * depending on how you wish to balance code size and function
 * call overheads.
 *
 *
 * \param  atomic         A pointer to the current RpAtomic
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \see RpAtomicPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
RpAtomicPS2AllObjInstanceTest(RpAtomic *atomic,
                              RxPS2AllPipeData *ps2AllPipeData)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllObjInstanceTest"));
    RpAtomicPS2AllObjInstanceTestMacro(atomic, ps2AllPipeData);
    RWRETURNVOID();
}

#undef RpAtomicPS2AllClear
#define RpAtomicPS2AllClear(_atomic) \
        RpAtomicPS2AllClearFunc(_atomic)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllClear is a macro to clear flags
 * related to instancing once an RpAtomic has been tested
 * for reinstancing.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for
 * RpAtomics, \ref RpAtomicPS2AllObjectSetupCallBack.
 * It clears dirty flags on the atomic's RpGeometry and
 * RpInterpolator once they have been tested to determine
 * if the RpAtomic needs reinstancing. The results of these
 * tests are cached in the objInstance member of the
 * \ref RxPS2AllPipeData struct, so they will persist
 * through rendering (and reinstance testing) of all
 * the meshes of the object. This macro 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
 * RpAtomicPS2AllClearMacro and in function
 * form through RpAtomicPS2AllClearFunc,
 * depending on how you wish to balance code size and
 * function call overheads.
 *
 *
 * \param  atomic         A pointer to the current RpAtomic
 *
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 * \see RpAtomicPS2AllObjectSetupCallBack
 */
void
RpAtomicPS2AllClear(RpAtomic *atomic)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllClear"));
    RpAtomicPS2AllClearMacro(atomic);
    RWRETURNVOID();
}

#undef RpAtomicPS2AllTransformSetup
#define RpAtomicPS2AllTransformSetup(_atomic, _transform) \
        RpAtomicPS2AllTransformSetupFunc(_atomic, _transform)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllTransformSetup is a macro
 * to set up the transformation matrix for an RpAtomic.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RpAtomics,
 * \ref RpAtomicPS2AllObjectSetupCallBack. It concatenates
 * the atomic's LTM 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
 * RpAtomicPS2AllTransformSetupMacro and in function
 * form through RpAtomicPS2AllTransformSetupFunc,
 * depending on how you wish to balance code size and
 * function call overheads.
 *
 *
 * \param  atomic         A pointer to the current RpAtomic
 * \param  transform      A pointer to a pointer to the \ref RwMatrix
 *                        holding the object-space to camera-space
 *                        transform for the current RpAtomic
 *
 * \see RpAtomicPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
RpAtomicPS2AllTransformSetup(RpAtomic *atomic,
                             RwMatrix **transform)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllTransformSetup"));
    RpAtomicPS2AllTransformSetupMacro(atomic, transform);
    RWRETURNVOID();
}

#undef RpAtomicPS2AllFrustumTest
#define RpAtomicPS2AllFrustumTest(_atomic, _inFrustum) \
        RpAtomicPS2AllFrustumTestFunc(_atomic, _inFrustum)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllFrustumTest is a macro to test
 * an RpAtomic 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 RpAtomics,
 * \ref RpAtomicPS2AllObjectSetupCallBack. It tests the
 * bounding sphere of the current RpAtomic
 * (see \ref RpAtomicGetWorldBoundingSphere) against
 * the frustum of the current RwCamera (see
 * \ref RwCameraFrustumTestSphere). 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
 * RpAtomicPS2AllFrustumTestMacro and in function
 * form through RpAtomicPS2AllFrustumTestFunc,
 * depending on how you wish to balance code size and
 * function call overheads.
 *
 *
 * \param  atomic         A pointer to the current RpAtomic
 * \param  inFrustum      A pointer to a \ref RwFrustumTestResult
 *                        to receive the result of a frustum test
 *                        on the atomic
 *
 * \see RpAtomicPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
RpAtomicPS2AllFrustumTest(RpAtomic *atomic,
                          RwFrustumTestResult *inFrustum)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllFrustumTest"));
    RpAtomicPS2AllFrustumTestMacro(atomic, inFrustum);
    RWRETURNVOID();
}

#undef RpAtomicPS2AllPrimTypeTransTypeSetup
#define RpAtomicPS2AllPrimTypeTransTypeSetup(_ps2AllPipeData, _inFrustum) \
        RpAtomicPS2AllPrimTypeTransTypeSetupFunc(_ps2AllPipeData, _inFrustum)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllPrimTypeTransTypeSetup is a macro to
 * select the type of transform and GS primitive for
 * rendering an RpAtomic.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RpAtomics,
 * \ref RpAtomicPS2AllObjectSetupCallBack. Based on the
 * result of a prior camera frustum test on the atomic and
 * the atomic's RpMeshHeader (which defines the primitive
 * type of the atomic's geometry), it sets the transType and
 * primType members of the \ref RxPS2AllPipeData struct as
 * appropriate. These are used later on in the pipeline,
 * repsectively, to select VU1 code and set GS primitive type.
 *
 * Currently, this macro is shared with the RpWorldSector and
 * RwIm3D default pipelines, because the appropriate logic is
 * the same in all cases. 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
 * RpAtomicPS2AllPrimTypeTransTypeSetupMacro and in function
 * form through RpAtomicPS2AllPrimTypeTransTypeSetupFunc,
 * depending on how you wish to balance code size and
 * function call overheads.
 *
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 * \param  inFrustum      A \ref RwFrustumTestResult holding the
 *                        result of a frustum test on the atomic
 *
 * \see RpAtomicPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
RpAtomicPS2AllPrimTypeTransTypeSetup(RxPS2AllPipeData *ps2AllPipeData,
                                     RwFrustumTestResult inFrustum)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllPrimTypeTransTypeSetup"));
    RpAtomicPS2AllPrimTypeTransTypeSetupMacro(ps2AllPipeData, inFrustum);
    RWRETURNVOID();
}

#undef RpAtomicPS2AllMatModulateSetup
#define RpAtomicPS2AllMatModulateSetup(_atomic, ps2AllPipeData) \
        RpAtomicPS2AllMatModulateSetupFunc(_atomic, ps2AllPipeData)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllMatModulateSetup is a macro
 * to determine whether material color is to be modulated for
 * an RpAtomic.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RpAtomics,
 * \ref RpAtomicPS2AllObjectSetupCallBack. It sets up the
 * matModulate member of the \ref RxPS2AllPipeData struct
 * depending on the flags of the current atomic's RpGeometry.
 * 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
 * RpAtomicPS2AllMatModulateSetupMacro and in function
 * form through RpAtomicPS2AllMatModulateSetupFunc,
 * depending on how you wish to balance code size and function
 * call overheads.
 *
 *
 * \param  atomic         A pointer to the current RpAtomic
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \see RpAtomicPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 */
void
RpAtomicPS2AllMatModulateSetup(RpAtomic *atomic,
                               RxPS2AllPipeData *ps2AllPipeData)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllMatModulateSetup"));
    RpAtomicPS2AllMatModulateSetupMacro(atomic, ps2AllPipeData);
    RWRETURNVOID();
}

#undef RpAtomicPS2AllLightingSetup
#define RpAtomicPS2AllLightingSetup(_ps2AllPipeData, _lightingFunc) \
        RpAtomicPS2AllLightingSetupFunc(_ps2AllPipeData, _lightingFunc)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllLightingSetup is a macro to set up
 * lights to be used during an RpAtomic's transform on VU1.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllObjectSetupCallBack for RpAtomics,
 * \ref RpAtomicPS2AllObjectSetupCallBack. It iterates over
 * all lights which affect the given atomic and applies each
 * using the supplied \ref RxWorldApplyLightFunc (this sets
 * up an array of data which will be uploaded to VU1 prior
 * to the rendering of the atomic's meshes). It may be used
 * in constructing user callbacks.
 *
 * A more efficient helper macro which may be used in certain
 * circumstances is \ref RpAtomicPS2AllLightingPersist.
 *
 * This macro will be a function in a debug build, but it
 * may be used explicitly in macro form through the name
 * RpAtomicPS2AllLightingSetupMacro and in function form
 * through RpAtomicPS2AllLightingSetupFunc, depending on
 * how you wish to balance code size and function call
 * overheads.
 *
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 * \param  lightingFunc   A \ref RxWorldApplyLightFunc to upload
 *                        information on a light to VU1
 *
 * \see RpAtomicPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 * \see RpAtomicPS2AllLightingPersist
 */
void
RpAtomicPS2AllLightingSetup(RxPS2AllPipeData *ps2AllPipeData,
                            RxWorldApplyLightFunc lightingFunc)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllLightingSetup"));
    RpAtomicPS2AllLightingSetupMacro(ps2AllPipeData, lightingFunc);
    RWRETURNVOID();
}

#undef RpAtomicPS2AllLightingPersist
#define RpAtomicPS2AllLightingPersist(_inverse, _invScale) \
        RpAtomicPS2AllLightingPersistFunc(_inverse, _invScale)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllLightingPersist is a macro to allow
 * lighting data to persist in VU1 memory from a prior
 * pipeline execution.
 *
 * This is a helper macro which may be called from a user
 * \ref RxPipelineNodePS2AllObjectSetupCallBack. It is used
 * instead of \ref RpAtomicPS2AllLightingSetup (note: if one
 * is used, the other must not be!). The purpose is to improve
 * rendering efficiency by allowing PS2All to reuse lighting
 * data already in VU1 memory (uploaded during the rendering
 * of a prior object) for the current RpAtomic's rendering.
 *
 * Currently, this macro is usable from within RpWorldSector
 * pipelines as well as RpAtomic pipelines.
 *
 * This macro will be a function in a debug build, but it
 * may be used explicitly in macro form through the name
 * RpAtomicPS2AllLightingPersistMacro and in function form
 * through RpAtomicPS2AllLightingPersistFunc, depending on
 * how you wish to balance code size and function call
 * overheads.
 *
 *
 * \param  inverse  A pointer to a \ref RwMatrix specifying
 *                  the inverse of the current RpAtomic's LTM
 * \param  invScale A RwReal value specifying the (uniform)
 *                  scale in the inverse matrix
 *
 * \see RpAtomicPS2AllObjectSetupCallBack
 * \see RxPipelineNodePS2AllObjectSetupCallBack
 * \see RpAtomicPS2AllLightingSetup
 */
void
RpAtomicPS2AllLightingPersist(RwMatrix *inverse,
                              RwReal invScale)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllLightingPersist"));
    RpAtomicPS2AllLightingPersistMacro(inverse, invScale);
    RWRETURNVOID();
}

#undef RpAtomicPS2AllResEntryAlloc
#define RpAtomicPS2AllResEntryAlloc(_ps2AllPipeData, _repEntry, _size, _destroyCallBack) \
        RpAtomicPS2AllResEntryAllocFunc(_ps2AllPipeData, _repEntry, _size, _destroyCallBack)

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllResEntryAlloc is a macro to
 * allocate space for the instance data of an RpMesh in
 * an RpAtomic.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllMatResEntryAllocCallBack for
 * RpAtomics, \ref RpMeshPS2AllResEntryAllocCallBack. It
 * allocates space in the Resource Arena (see
 * \ref rwresourcesoverview for details) for the instance
 * data of the current mesh. Depending on whether the
 * parent RpAtomic is morph-animated or not (this affects
 * whether the resource entry is hung off the RpAtomic or
 * its RpGeometry), it allocates space and stores a
 * pointer to it in the appropriate location. 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
 * RpAtomicPS2AllResEntryAllocMacro and in function form
 * through RpAtomicPS2AllResEntryAllocFunc, depending on
 * how you wish to balance code size and function call
 * overheads.
 *
 *
 * \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 RpMeshPS2AllResEntryAllocCallBack
 * \see RxPipelineNodePS2AllMatResEntryAllocCallBack
 */
void
RpAtomicPS2AllResEntryAlloc(RxPS2AllPipeData *ps2AllPipeData,
                          RwResEntry **repEntry,
                          RwUInt32 size,
                          RwResEntryDestroyNotify destroyCallBack)
{
    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllResEntryAlloc"));
    RpAtomicPS2AllResEntryAllocMacro(
        ps2AllPipeData, repEntry,size, destroyCallBack);
    RWRETURNVOID();
}

#undef RpMeshPS2AllGatherMeshMetrics
#define RpMeshPS2AllGatherMeshMetrics(_ps2AllPipeData) \
        RpMeshPS2AllGatherMeshMetricsFunc(_ps2AllPipeData)

/**
 * \ingroup rpmeshsky2
 * \ref RpMeshPS2AllGatherMeshMetrics is a macro to gather
 * metrics information on the RpMesh currently being rendered.
 *
 * This is a helper macro which is called from the default
 * \ref RxPipelineNodePS2AllMatPostMeshCallBack for RpAtomics
 * and RpWorldSectors, \ref RpMeshPS2AllPostMeshCallBack. It
 * gathers metrics information on the RpMesh currently being
 * rendered (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
 * RpMeshPS2AllGatherMeshMetricsMacro and in function form
 * through RpMeshPS2AllGatherMeshMetricsFunc, depending on
 * how you wish to balance code size and function call
 * overheads.
 *
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 *
 * \see RpMeshPS2AllPostMeshCallBack
 * \see RxPipelineNodePS2AllMatPostMeshCallBack
 */
void
RpMeshPS2AllGatherMeshMetrics(RxPS2AllPipeData *ps2AllPipeData __RWUNUSEDUNLESSMETRICS__)
{
    RWAPIFUNCTION(RWSTRING("RpMeshPS2AllGatherMeshMetrics"));
    RpMeshPS2AllGatherMeshMetricsMacro(ps2AllPipeData);
    RWRETURNVOID();
}

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


/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllObjectSetupCallBack is the
 * \ref RxPipelineNodePS2AllObjectSetupCallBack used in the default
 * RenderWare RpAtomic pipeline.
 *
 * See \ref RxPipelineNodePS2AllObjectSetupCallBack for an overview
 * of this callback type. For RpAtomics, 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):
 *
 * \ref RpAtomicPS2AllGetMeshHeaderMeshCache, 
 * \ref RpAtomicPS2AllGatherObjMetrics,
 * \ref RpAtomicPS2AllMorphSetup,
 * \ref RpAtomicPS2AllObjInstanceTest,
 * \ref RpAtomicPS2AllTransformSetup,
 * \ref RpAtomicPS2AllFrustumTest,
 * \ref RpAtomicPS2AllPrimTypeTransTypeSetup,
 * \ref RpAtomicPS2AllMatModulateSetup,
 * \ref RpAtomicPS2AllLightingSetup
 *
 *
 * \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 RpAtomicPS2AllGetMeshHeaderMeshCache
 * \see RpAtomicPS2AllGatherObjMetrics
 * \see RpAtomicPS2AllMorphSetup
 * \see RpAtomicPS2AllObjInstanceTest
 * \see RpAtomicPS2AllTransformSetup
 * \see RpAtomicPS2AllFrustumTest
 * \see RpAtomicPS2AllPrimTypeTransTypeSetup
 * \see RpAtomicPS2AllMatModulateSetup
 * \see RpAtomicPS2AllLightingSetup
 */
RwBool
RpAtomicPS2AllObjectSetupCallBack(
    RxPS2AllPipeData *ps2AllPipeData,
    RwMatrix **transform,
    RxWorldApplyLightFunc lightingFunc)
{
/* TODO[3][5]: SEEMS POINTLESS PASSING lightingFunc, PS2AllSkyWorldApplyLight
 *   IS ALWAYS USED, NEVER CHANGED BUT THEN AGAIN, WE *COULD* CHANGE IT NOW,
 *  TRIVIALLY, AND IT DOES LITTLE HARM I GUESS. WOULD IT BE CHEAPER/SIMPLER
 *  TO JUST EXPOSE PS2AllSkyWorldApplyLight? */

    RpAtomic           *atomic;
    RwFrustumTestResult inFrustum;

    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllObjectSetupCallBack"));

    atomic = (RpAtomic *)(ps2AllPipeData->sourceObject);
    RWASSERT(NULL != atomic);

/* TODO[3]: THIS COMMENT MAKES NO SENSE! VERIFY ALL THESE TESTS AGAIN...
 *   ALSO, PERSISTENTLY-INSTANCED GEOMETRIES HAVE ZERO NUMVERTS/NUMTRIS
 *   SEE numMeshes TEST BELOW AND IN OTHER CALLBACKS TOO... */
    /* Check ASAP for an empty sector/atomic (atomic might have no
     * verts but plugin data for Bezier control points, for example) */
#if (0)
    if (RpGeometryGetNumVertices(RpAtomicGetGeometry(atomic)) <= 0)
    {
        /* Don't execute the rest of the pipeline */
        RWRETURN(FALSE);
    }
#endif /* (0) */

    /* Get the RwMeshCache from the atomic/geometry;
     * If the geometry has more than one morph target the resEntry
     * in the atomic is used else the resEntry in the geometry */
    RpAtomicPS2AllGetMeshHeaderMeshCache(atomic, ps2AllPipeData);

    /* Early out if no meshes */
    if(ps2AllPipeData->meshHeader->numMeshes <= 0)
    {
        /* 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/ */
    /* TODO[3]: IF IT'S POTENTIALLY VALID, WHY ON EARTH EARLY-OUT? */
        RWRETURN(FALSE);
    }

    /* Gather metrics */
    RpAtomicPS2AllGatherObjMetrics(atomic);

    /* Set up numMorphTargets and spExtra */
    RpAtomicPS2AllMorphSetup(atomic, ps2AllPipeData);

    /* Decide whether this geometry needs instancing or not */
    RpAtomicPS2AllObjInstanceTest(atomic, ps2AllPipeData);

    /* Clear geometry and interpolator instance flags */
    RpAtomicPS2AllClear(atomic);

    /* We need to cache the transform */
    RpAtomicPS2AllTransformSetup(atomic, transform);

    /* We need to to a frustum test here */
    RpAtomicPS2AllFrustumTest(atomic, &inFrustum);

    /* Set up primType and transtype */
    RpAtomicPS2AllPrimTypeTransTypeSetup(ps2AllPipeData, inFrustum);

    /* Do we modulate with material colours for this geometry? */
    RpAtomicPS2AllMatModulateSetup(atomic, ps2AllPipeData);

    /* Do da lightin', boss */
    RpAtomicPS2AllLightingSetup(ps2AllPipeData, lightingFunc);

    RWRETURN(TRUE);
}

/**
 * \ingroup rpmeshsky2
 * \ref RpMeshPS2AllMeshInstanceTestCallBack is the
 * \ref RxPipelineNodePS2AllMatMeshInstanceTestCallBack used
 * in the default RenderWare RpMaterial pipeline.
 *
 * See \ref RxPipelineNodePS2AllMatMeshInstanceTestCallBack for an
 * overview of this callback type. For RpAtomics and RpWorldSectors,
 * 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:
 *
 * \ref RpMeshPS2AllTestNumVerts, 
 * \ref RpMeshPS2AllTestMeshID
 *
 *
 * \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 RpMeshPS2AllTestNumVerts
 * \see RpMeshPS2AllTestMeshID
 */
RwBool
RpMeshPS2AllMeshInstanceTestCallBack(RxPS2AllPipeData *ps2AllPipeData)
{
    RwResEntry *resEntry;
    rwPS2AllResEntryHeader *ps2AllResHeader;

    RWAPIFUNCTION(RWSTRING("RpMeshPS2AllMeshInstanceTestCallBack"));

    REDEBUGPrintf(("parent is an atomic\n"));

/* TODO[3]: IS THIS FUNC O.T.T? WHAT IS REQUIRED TO MAKE numVerts CHANGE?
 *   (I SUSPECT EDITING POLYGONS, WHICH WILL SET rpGEOMETRYLOCKPOLYGONS IN
 *  geometry->lockedSinceLastInst AND THAT CAUSES A FULL REINSTANCE IN THE
 *  OBJECT SETUP CODE ABOVE... SO IS CHECKING NUMVERTS ENTIRELY REDUNDANT??)
 *   AND meshHeader->flags? CAN'T WE JUST STORE AND TEST THESE THINGS IN
 *  DEBUG MODE? SEE TOP OF ps2allatomic.h, NEED TO RATIONALISE INSTANCE TESTS
 *  (HOPEFULLY CAN LIST THE HOTSPOTS AND MAKE IT A TASK THAT GETS PUT IN THE
 *  MAIN RW(4?) PROPOSAL LIST)*/

    resEntry = *(ps2AllPipeData->cacheEntryRef);
    /* We should always have an old resEntry to test against */
    RWASSERT(NULL != resEntry);
    ps2AllResHeader = RWPS2ALLRESENTRYHEADERFROMRESENTRY(resEntry);

    RpMeshPS2AllTestNumVerts(ps2AllPipeData);

    /* Full is as full as full can be */
    if (ps2AllPipeData->meshInstance & rxINSTANCEFULLINSTANCE) RWRETURN(TRUE);

/* TODO[3][5]: Umm... I hate to mention it, but this meshID only contains
 *             the flags of the RpMeshHeader and that is a PER-OBJECT thing!!! */
    RpMeshPS2AllTestMeshID(ps2AllPipeData);

    RWRETURN(TRUE);
}

/**
 * \ingroup rpmeshsky2
 * \ref RpMeshPS2AllResEntryAllocCallBack is the
 * \ref RxPipelineNodePS2AllMatResEntryAllocCallBack used
 * in the default RenderWare RpMaterial pipeline.
 *
 * See \ref RxPipelineNodePS2AllMatResEntryAllocCallBack for an
 * overview of this callback type. For RpAtomics and RpWorldSectors,
 * this default callback is composed of the following helper macros
 * (of which you may use any or all in constructing your own
 * replacement callback):
 *
 * \li RPWORLDSECTORVERIFY (to detect sectors/atomics)
 * \li \ref RpWorldSectorPS2AllResEntryAlloc (for sectors)
 * \li \ref RpAtomicPS2AllResEntryAlloc (for atomics)
 *
 *
 * \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)
 *
 * \return A pointer to a \ref RwResEntry on success, NULL otherwise
 *
 * \see RxPipelineNodePS2AllMatResEntryAllocCallBack
 * \see RpWorldSectorPS2AllResEntryAlloc
 * \see RpAtomicPS2AllResEntryAlloc
 */
RwResEntry *
RpMeshPS2AllResEntryAllocCallBack(RxPS2AllPipeData *ps2AllPipeData,
                                  RwResEntry **repEntry,
                                  RwUInt32 size,
                                  RwResEntryDestroyNotify destroyCallBack)
{
    RWAPIFUNCTION(RWSTRING("RpMeshPS2AllResEntryAllocCallBack"));

    RWASSERT(NULL != ps2AllPipeData);

    /* We have to differentiate between RpAtomics and RpWorldSectors w.r.t both
     * instancing and resource entry allocation, given remaining differences
     * prior to the eventual RW4 unification. We still have to make sure that
     * the default material pipelines (mat->pipe == NULL) "just work" for both
     * atomics and sectors, so we have to do something automatic in here that
     * the user can't mess up by changing the object pipeline. Luckily, we can
     * use the fact that RpAtomic has an RwObjectHasFrame at its head, which
     * in turn has an RwObject. The first member of this, the RwUInt8 'type'
     * is set to rpATOMIC (value 1). RpWorldSector has a RwInt32 'type' at its
     * head which is set to rwSECTORATOMIC (value -1), so regardless of
     * endianness, we can count on *(RwUInt8 *)object to be '-1' only in the
     * case of sectors :) */
/* TODO[7]: BE MORE ELEGANT WHEN RW4 ARRIVES :)  [DITTO IN INSTANCECB] */
    if (RPWORLDSECTORVERIFY(ps2AllPipeData->sourceObject))
    {
        /* WorldSector [sector->type == rwSECTORATOMIC == -1] */
        RpWorldSectorPS2AllResEntryAlloc(ps2AllPipeData, repEntry, size, destroyCallBack);
    }
    else
    {
        /* Atomic [atomic->object->object->type == rpATOMIC == 1] */
        RpAtomicPS2AllResEntryAlloc(ps2AllPipeData, repEntry, size, destroyCallBack);
    }

    RWRETURN(*repEntry);
}

/**
 * \ingroup rpmeshsky2
 * \ref RpMeshPS2AllInstanceCallBack is the
 * \ref RxPipelineNodePS2AllMatInstanceCallBack used
 * in the default RenderWare RpMaterial pipeline.
 *
 * See \ref RxPipelineNodePS2AllMatInstanceCallBack for an
 * overview of this callback type. For RpAtomics and
 * RpWorldSectors, this default callback uses the macro
 * RPWORLDSECTORVERIFY to differentiate between RpAtomics
 * and RpWorldSectors and calls the appropriate default
 * instance function (RpWorldSectorPS2AllInstance or
 * RpAtomicPS2AllInstance). These may be used in
 * constructing your own replacement callback.
 *
 *
 * \param  ps2AllPipeData A pointer to a \ref RxPS2AllPipeData struct
 *                        containing information relevant to the
 *                        current pipeline execution
 * \param  clusterData    An array of void pointers to the data of
 *                        visible clusters
 * \param  numClusters    The length of the clusterData array
 *
 * \return TRUE on success, FALSE otherwise
 *
 * \see RxPipelineNodePS2AllMatInstanceCallBack
 */
RwBool
RpMeshPS2AllInstanceCallBack(RxPS2AllPipeData *ps2AllPipeData,
                             void ** __RWUNUSED__ clusters,
                             RwUInt32 __RWUNUSED__ numClusters)
{
    RWAPIFUNCTION(RWSTRING("RpMeshPS2AllInstanceCallBack"));

    RWASSERT(NULL != ps2AllPipeData);
    /* See above RpMeshPS2AllResEntryAllocCallBack on atomic/sector differentiation */
    if (RPWORLDSECTORVERIFY(ps2AllPipeData->sourceObject))
    {
        /* WorldSector [sector->type == rwSECTORATOMIC == -1] */
        REDEBUGPrintf(("parent is a world sector\n"));
        RWRETURN(RpWorldSectorPS2AllInstance(ps2AllPipeData));
    }
    else
    {
        /* Atomic [atomic->object->object->type == rpATOMIC == 1] */
        REDEBUGPrintf(("parent is an atomic\n"));
        RWRETURN(RpAtomicPS2AllInstance(ps2AllPipeData));
    }

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

/**
 * \ingroup rpmeshsky2
 * \ref RpMeshPS2AllBridgeCallBack is the
 * \ref RxPipelineNodePS2AllMatBridgeCallBack used in the
 * default RenderWare RpMaterial pipeline.
 *
 * See \ref RxPipelineNodePS2AllMatBridgeCallBack for an overview
 * of this callback type. For RpAtomics and RpWorldSectors, this
 * default callback is composed of the following helper macros
 * (of which you may use some or all in constructing your own
 * replacement callback), in the order shown here (you should keep
 * the same order, as explained in \ref RpMeshPS2AllStartVIFUploads):
 *
 * \li \ref RpMeshPS2AllAsyncTextureUpload,
 * \li \ref RpMeshPS2AllSyncTextureUpload,
 * \li \ref RpMeshPS2AllStartVIFUploads,
 * \li \ref RpMeshPS2AllGIFTagUpload,
 * \li \ref RpMeshPS2AllMatColUpload,
 * \li \ref RpMeshPS2AllSurfPropsUpload,
 * \li \ref RpMeshPS2AllClipInfoUpload,
 * \li \ref RpMeshPS2AllTextureStateUpload,
 * \li \ref RpMeshPS2AllVU1CodeIndexSetup,
 * \li \ref RpMeshPS2AllVU1CodeUpload,
 * \li \ref RpMeshPS2AllEndVIFUploads
 *
 *
 * \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 RpMeshPS2AllAsyncTextureUpload,
 * \see RpMeshPS2AllSyncTextureUpload,
 * \see RpMeshPS2AllStartVIFUploads,
 * \see RpMeshPS2AllGIFTagUpload,
 * \see RpMeshPS2AllMatColUpload,
 * \see RpMeshPS2AllSurfPropsUpload,
 * \see RpMeshPS2AllClipInfoUpload,
 * \see RpMeshPS2AllTextureStateUpload,
 * \see RpMeshPS2AllVU1CodeIndexSetup,
 * \see RpMeshPS2AllVU1CodeUpload,
 * \see RpMeshPS2AllEndVIFUploads
 */
RwBool
RpMeshPS2AllBridgeCallBack(RxPS2AllPipeData *ps2AllPipeData)
{
    RwUInt32 numInitialQW, numExtraQW;

    RWAPIFUNCTION(RWSTRING("RpMeshPS2AllBridgeCallBack"));


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

    /* Asynchronously upload the texture if nec/poss */
    RpMeshPS2AllAsyncTextureUpload(ps2AllPipeData);
    /* If changed, does skyTexCacheAccessRaster and updates global renderstate */
    RpMeshPS2AllSyncTextureUpload(ps2AllPipeData);

    /* Open a VIF transfer packet, with a header DMA tag */
    numInitialQW = rpMESHPS2ALLGIFTAGNUMINITIALQW +
                   rpMESHPS2ALLMATCOLNUMINITIALQW +
                   rpMESHPS2ALLSURFPROPSNUMINITIALQW +
                   rpMESHPS2ALLCLIPINFONUMINITIALQW +
                   rpMESHPS2ALLTEXTURESTATENUMINITIALQW;
    numExtraQW = rpMESHPS2ALLVU1CODEUPLOADNUMEXTRAQW;
    RpMeshPS2AllStartVIFUploads(numInitialQW, numExtraQW);


    /* 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) */
/* TODO[5]: CHECK HELPER MACROS - IF CAN PUT IN PREDICATION PARAMETERS TO HELP
 *   CULL WORK, THEN DO SO - EG FOR RpMeshPS2AllMatColUpload, CAN SAY "YES/NO"
 *   IN THE PARAMETERS, FOR WHETHER THERE'S A TEXTURE (DEPENDING ON WHETHER
 *   YOU KNOW) AND THAT'LL BOIL AWAY IN RELEASE COS IT'S A MACRO! [SHOULD BE
 *   ABLE TO ADD SUCH PARAMETERS LATER WITH LEGACY-SUPPORT MACROS THAT CONTAIN
 *   THE BOILED-AWAY TESTS] */
    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);

    /* Upload texture renderstate to the GS (this sends stuff thru VIF
     * only, ergo can be combined with other VIF stuff! :) ) */
    RpMeshPS2AllTextureStateUpload(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);
}

/**
 * \ingroup rpmeshsky2
 * \ref RpMeshPS2AllPostMeshCallBack is the
 * \ref RxPipelineNodePS2AllMatPostMeshCallBack used in the default
 * RenderWare RpMaterial pipeline.
 *
 * See \ref RxPipelineNodePS2AllMatPostMeshCallBack for an overview
 * of this callback type. For RpAtomics and RpWorldSectors, this
 * default callback (only actually used in RWMETRICS builds) is
 * composed of the following helper macro (which you may use in
 * constructing your own replacement callback):
 *
 * \ref RpMeshPS2AllGatherMeshMetrics
 *
 *
 * \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 RpMeshPS2AllGatherMeshMetrics
 */
RwBool
RpMeshPS2AllPostMeshCallBack(RxPS2AllPipeData *ps2AllPipeData
#if (!defined(RWMETRICS))
__RWUNUSED__
#endif /* (!defined(RWMETRICS)) */
                    )
{
    RWAPIFUNCTION(RWSTRING("RpMeshPS2AllPostMeshCallBack"));

    /* Gather per-mesh metrics */
    RpMeshPS2AllGatherMeshMetrics(ps2AllPipeData);

    RWRETURN(TRUE);
}

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

RpLight *
_rpAtomicPS2AllDoApplyLight(RpLight *light, void *pData)
{
    RWAPIFUNCTION(RWSTRING("_rpAtomicPS2AllDoApplyLight"));
    RWASSERT(light);
    RWASSERTISTYPE(light, rpLIGHT);

    if (rwObjectTestFlags(light, rpLIGHTLIGHTATOMICS))
    {
        rpAtomicPS2AllLightData *lightingData = (rpAtomicPS2AllLightData *) pData;

        RWASSERT(lightingData);

        lightingData->lightFunc(light,
                               &lightingData->invMat,
                                lightingData->invScale,
                                lightingData->surface);
    }

    RWRETURN(light);
}

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

RpLight *
_rpAtomicPS2AllDoApplyLightFrame(RpLight *light, void *pData)
{
    RWAPIFUNCTION(RWSTRING("_rpAtomicPS2AllDoApplyLightFrame"));
    RWASSERT(light);
    RWASSERTISTYPE(light, rpLIGHT);

    if (light->lightFrame != RWSRCGLOBAL(lightFrame))
    {
        if (rwObjectTestFlags(light, rpLIGHTLIGHTATOMICS))
        {
            rpAtomicPS2AllLightData *lightingData = (rpAtomicPS2AllLightData *)pData;

            RWASSERT(lightingData);

            lightingData->lightFunc(light,
                                   &lightingData->invMat,
                                    lightingData->invScale,
                                    lightingData->surface);
        }

        light->lightFrame = RWSRCGLOBAL(lightFrame);
    }

    RWRETURN(light);
}

/**
 * \ingroup rpatomicsky2
 * \ref RpAtomicPS2AllInstance [ToDo]
 */
RwBool
RpAtomicPS2AllInstance(RxPS2AllPipeData *ps2AllPipeData)
{
/* TODO[4]: ADD NEW REINSTANCING DATA BLOCK TO THE PARAMETERS */
    RpInterpolator *interpolator;
    RpGeometry *geom;
    rwPS2AllResEntryHeader *ps2AllResHeader;
    RwUInt32 numVerts;

    /* These next two MUST be signed or they'll wrap around causing infinite loops!! */
    RwInt32 vertCounter, j;
    RwUInt32 stripTmp = 0;
    RwBool instanceXYZ, instanceNormal;
    RpMorphTarget *startMorphTarget, *endMorphTarget;
#if (!defined(FASTMORPH))
    RwReal scale;
#endif /* (!defined(FASTMORPH)) */
    INDEXDECLARE();

    RWAPIFUNCTION(RWSTRING("RpAtomicPS2AllInstance"));


    interpolator = &(((RpAtomic *)(ps2AllPipeData->sourceObject))->interpolator);
    geom = RpAtomicGetGeometry((RpAtomic *)(ps2AllPipeData->sourceObject));
    ps2AllResHeader = RWPS2ALLRESENTRYHEADERFROMRESENTRY(
                       *(ps2AllPipeData->cacheEntryRef));
    numVerts = ps2AllResHeader->numVerts;


    REDEBUGPrintf(("RpAtomicPS2AllInstance"));
    REDEBUGPrintf(("numVerts: %d\n", numVerts));
    REDEBUGPrintf(("batchSize: %d\n", ps2AllResHeader->batchSize));

/* TODO[3][5]: WOULD BE IDEAL TO SHARE THIS FUNCTION BETWEEN ATOMICS AND
 *  SECTORS (IT'D SAVE LOTS OF DUPLICATE CODE). YOU ONLY NEED TO PREDICATE
 *  NORMALS AND MORPHING ON ATOMIC/SECTORS (USING RPWORLDSECTORVERIFY()) */


/* TODO[3]: SHOULDN'T STRIPTMP BE HIDDEN IN THE INSTANCING MACROS? */
    if (ps2AllPipeData->meshHeader->flags & rpMESHHEADERTRISTRIP) stripTmp = 2;
    INDEXSETUP(ps2AllPipeData->meshHeader->flags, ps2AllPipeData->mesh->indices);


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

    instanceXYZ = FALSE;
    if ((  ps2AllPipeData->meshInstance & rxINSTANCEXYZ) &&
        (  ps2AllPipeData->matPvtData->clinfo[CL_XYZ].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(ps2AllPipeData->matPvtData->clinfo[CL_XYZ].attrib & CL_ATTRIB_DONT_FILL)))
    {
        /* RWASSERT(rwObjectTestFlags(geom, rpGEOMETRYPOSITIONS)); */
        instanceXYZ = TRUE;
    }

    instanceNormal = FALSE;
    if ((  ps2AllPipeData->meshInstance & rxINSTANCENORMAL) &&
        (  ps2AllPipeData->matPvtData->clinfo[CL_NORMAL].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(ps2AllPipeData->matPvtData->clinfo[CL_NORMAL].attrib & CL_ATTRIB_DONT_FILL)))
    {
        /* 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);

#if (!defined(FASTMORPH))
    if (interpolator->startMorphTarget == interpolator->endMorphTarget)
#endif /* (!defined(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 = ps2AllResHeader->data +
                   ps2AllResHeader->fieldRec[CL_XYZ].dataoffset;
#if (defined(FASTMORPH))
            if (geom->numMorphTargets > 1)
            {
                data = ps2AllResHeader->data +
                       ps2AllResHeader->fieldRec[CL_XYZ].morphDataoffset;
            }
#endif /* (defined(FASTMORPH)) */
            dataTmp = (RwReal *)data;

            INDEXRESET();

            vertCounter = numVerts;
            while (vertCounter > 0)
            {
                if (vertCounter <= (RwInt32)ps2AllResHeader->batchSize)
                {
                    data -= ps2AllResHeader->fieldRec[CL_XYZ].reverse;
                    dataTmp = (RwReal *)data;
                    j = vertCounter;
                    vertCounter = 0;
                }
                else
                {
                    j = ps2AllResHeader->fieldRec[CL_XYZ].numVerts;
#if (defined(FASTMORPH))
                    if (geom->numMorphTargets > 1)
                    {
                        j = ps2AllResHeader->fieldRec[CL_XYZ].morphNumVerts;
                    }
#endif /* (defined(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 = ps2AllResHeader->fieldRec[CL_XYZ].skip;
#if (defined(FASTMORPH))
                if (geom->numMorphTargets > 1)
                {
                    j = ps2AllResHeader->fieldRec[CL_XYZ].morphSkip;
                }
#endif /* (defined(FASTMORPH)) */
                data += j;
                dataTmp = (RwReal *)data;
            }
        }

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

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

            INDEXRESET();

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

                if (rwObjectTestFlags(geom, rpGEOMETRYNORMALS))
                {
                    if (ps2AllPipeData->matPvtData->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 (ps2AllPipeData->matPvtData->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= ps2AllResHeader->fieldRec[CL_NORMAL].skip;
#if (defined(FASTMORPH))
                if (geom->numMorphTargets > 1)
                {
                    j = ps2AllResHeader->fieldRec[CL_NORMAL].morphSkip;
                }
#endif /* (defined(FASTMORPH)) */
                data += j;
                dataTmp = (RwUInt8 *)data;
            }
        }
    }
#if (!defined(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 = (ps2AllResHeader->data
                              + ps2AllResHeader->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)ps2AllResHeader->batchSize)
                {
                    data -= ps2AllResHeader->fieldRec[CL_XYZ].reverse;
                    dataTmp = (RwReal *)data;
                    j = vertCounter;
                    vertCounter = 0;
                }
                else
                {
                    j = ps2AllResHeader->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 += ps2AllResHeader->fieldRec[CL_XYZ].skip;
                dataTmp = (RwReal *)data;
            }
        }

        if (FALSE != instanceNormal)
        {
            u_long128 *data = (ps2AllResHeader->data
                           + ps2AllResHeader->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)ps2AllResHeader->batchSize)
                {
                    data -= ps2AllResHeader->fieldRec[CL_NORMAL].reverse;
                    dataTmp = (RwUInt8 *)data;
                    j = vertCounter;
                    vertCounter = 0;
                }
                else
                {
                    j = ps2AllResHeader->fieldRec[CL_NORMAL].numVerts;
                    vertCounter -= (j - stripTmp);
                }

                if (rwObjectTestFlags(geom, rpGEOMETRYNORMALS))
                {
                    if (ps2AllPipeData->matPvtData->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 (ps2AllPipeData->matPvtData->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 += ps2AllResHeader->fieldRec[CL_NORMAL].skip;
                dataTmp = (RwUInt8 *)data;
            }
        }
    }
#else /* (!defined(FASTMORPH)) */
    ps2AllResHeader->morphStart = interpolator->startMorphTarget;
    ps2AllResHeader->morphFinish = interpolator->endMorphTarget;
    ps2AllResHeader->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 = ps2AllResHeader->data +
                  ps2AllResHeader->fieldRec[CL_MAXCL].morphDataoffset;
            dataTmp = (RwReal *)data;

            INDEXRESET();

            vertCounter = numVerts;
            while (vertCounter > 0)
            {
                if (vertCounter <= (RwInt32)ps2AllResHeader->batchSize)
                {
                    data -= ps2AllResHeader->fieldRec[CL_MAXCL].reverse;
                    dataTmp = (RwReal *)data;
                    j = vertCounter;
                    vertCounter = 0;
                }
                else
                {
                    j = ps2AllResHeader->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 += ps2AllResHeader->fieldRec[CL_MAXCL].morphSkip;
                dataTmp = (RwReal *)data;
            }
        }
        if (FALSE != instanceNormal)
        {
            u_long128 *data;
            RwUInt8 *dataTmp;
            RwV3d   *normals = endMorphTarget->normals;

            data = ps2AllResHeader->data +
                  ps2AllResHeader->fieldRec[CL_MAXCL+1].morphDataoffset;
            dataTmp = (RwUInt8 *)data;

            INDEXRESET();

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

                if (rwObjectTestFlags(geom, rpGEOMETRYNORMALS))
                {
                    if (ps2AllPipeData->matPvtData->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 (ps2AllPipeData->matPvtData->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 += ps2AllResHeader->fieldRec[CL_MAXCL+1].morphSkip;
                dataTmp = (RwUInt8 *)data;
            }
        }
    }
#endif /* (!defined(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 ((  ps2AllPipeData->meshInstance & (rxINSTANCEUV | rxINSTANCEUV2)) &&
        (  ps2AllPipeData->matPvtData->clinfo[CL_UV].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(ps2AllPipeData->matPvtData->clinfo[CL_UV].attrib & CL_ATTRIB_DONT_FILL)) &&
        rwObjectTestFlags(geom, rpGEOMETRYTEXTURED|rpGEOMETRYTEXTURED2) )
    {
        u_long128 *data;
        RwReal *dataTmp;
        RwTexCoords *tc = geom->texCoords[0];

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

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

        INDEXRESET();

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

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

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

                INDEXINC();
            }

            STRIPREVERSE(stripTmp);

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

    if ((  ps2AllPipeData->meshInstance & rxINSTANCEUV2) &&
        (  ps2AllPipeData->matPvtData->clinfo[CL_UV2].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(ps2AllPipeData->matPvtData->clinfo[CL_UV2].attrib & CL_ATTRIB_DONT_FILL)) &&
        rwObjectTestFlags(geom, rpGEOMETRYTEXTURED2))
    {
        u_long128 *data;
        RwReal *dataTmp;
        RwTexCoords *tc1 = geom->texCoords[0];
        RwTexCoords *tc2 = geom->texCoords[1];

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

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

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2AllResHeader->batchSize)
            {
                data -= ps2AllResHeader->fieldRec[CL_UV2].reverse;
                dataTmp = (RwReal *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2AllResHeader->fieldRec[CL_UV2].numVerts;
#if (defined(FASTMORPH))
                if (geom->numMorphTargets > 1)
                {
                    j = ps2AllResHeader->fieldRec[CL_UV2].morphNumVerts;
                }
#endif /* (defined(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 = ps2AllResHeader->fieldRec[CL_UV2].skip;
#if (defined(FASTMORPH))
            if (geom->numMorphTargets > 1)
            {
                j = ps2AllResHeader->fieldRec[CL_UV2].morphSkip;
            }
#endif /* (defined(FASTMORPH)) */
            data += j;
            dataTmp = (RwReal *)data;
        }
    }

    if ((  ps2AllPipeData->meshInstance & rxINSTANCERGBA) &&
        (  ps2AllPipeData->matPvtData->clinfo[CL_RGBA].attrib & CL_ATTRIB_REQUIRED  ) &&
        (!(ps2AllPipeData->matPvtData->clinfo[CL_RGBA].attrib & CL_ATTRIB_DONT_FILL)))
    {
        u_long128 *data;
        RwUInt32 *dataTmp;
        RwRGBA *preLitLum = geom->preLitLum;

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

        INDEXRESET();

        vertCounter = numVerts;
        while (vertCounter > 0)
        {
            if (vertCounter <= (RwInt32)ps2AllResHeader->batchSize)
            {
                data -= ps2AllResHeader->fieldRec[CL_RGBA].reverse;
                dataTmp = (RwUInt32 *)data;
                j = vertCounter;
                vertCounter = 0;
            }
            else
            {
                j = ps2AllResHeader->fieldRec[CL_RGBA].numVerts;
#if (defined(FASTMORPH))
                if (geom->numMorphTargets > 1)
                {
                    j = ps2AllResHeader->fieldRec[CL_RGBA].morphNumVerts;
                }
#endif /* (defined(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 = ps2AllResHeader->fieldRec[CL_RGBA].skip;
#if (defined(FASTMORPH))
            if (geom->numMorphTargets > 1)
            {
                j = ps2AllResHeader->fieldRec[CL_RGBA].morphSkip;
            }
#endif /* (defined(FASTMORPH)) */
            data += j;
            dataTmp = (RwUInt32 *)data;
        }
    }

    REDEBUGObjectDMADumpMacro();

    RWRETURN(TRUE);
}

