R E T I R E D






/*
 * _doc nodePS2AtomicInstance
 * _topic TODO: nodePS2AtomicInstance |
 * _index | nodePS2AtomicInstance
 * _normal Copyright (c) Criterion Software Limited
 */
/****************************************************************************
 *                                                                          *
 * module : nodePS2AtomicInstance.c                                         *
 *                                                                          *
 * purpose: yawn...                                                         *
 *                                                                          *
 ****************************************************************************/

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

#include <math.h>
#include <eekernel.h> /* SyncDCache(), etc. */
#include "rwcore.h"
#include "baworld.h"
#include "p2stdclsw.h"
#include "skyisms.h"
#include "nodePS2AtomicInstance.h"

static const char rcsid[] __RWUNUSED__ = "@@(#)$Id: nodePS2AtomicInstance.c,v 1.28 2000/09/29 16:08:26 iestynb Exp $";


/****************************************************************************
 local types
 */

/****************************************************************************
 local (static) prototypes
 */


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

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

/************************************************/
/**                                            **/
/**  PS2ATOMICINSTANCE.CSL NODE SPECIFICATION  **/
/**                                            **/
/************************************************/

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

/****************************************************************************
 functions
 */

/****************************************************************************
 _rwSkyInstanceDestroyCallback

 On entry : resRntry
 On exit  :

 If the Instance has a reference count, wait for it to reach 0, flushing as
 required.
 */

static void
_rwSkyInstanceDestroyCallback(RwResEntry *resEntry)
{
    volatile RwUInt32 *count = SKYINSTANCEREFCNT(resEntry);
    RWFUNCTION(RWSTRING("_rwSkyInstanceDestroyCallback"));

    PFENTRY(PF_rwSkyInstanceDestroyCallback);
    while (*count != 0)
    {
        PFCALL(PF_rwSkyInstanceDestroyCallback);
        _sweFlush();
        PFRET(PF_rwSkyInstanceDestroyCallback);
    }
    PFEXIT(PF_rwSkyInstanceDestroyCallback);

    RWRETURNVOID();
}

/****************************************************************************
 _rwQueuePS2DMAChainPacket
 */

static RwBool
_rwQueuePS2DMAChainPacket(RxPipelineNodeInstance *self, RwResEntry *repEntry)
{
    RxPacket *pk;

    RWFUNCTION(RWSTRING("_rwQueuePS2DMAChainPacket"));

    if ( (pk = RxPacketCreate(self)) != NULL )
    {
        RxCluster *cl;

        if ( (cl = RxClusterLockWrite(pk,
                                      0 /* RxClPS2DMAChain */, 
                                      self)) != NULL )
        {
            /* as a rule, we're pretty scornful of creating clusters by hand */

            cl->flags      = rxCLFLAGS_CLUSTERVALID | rxCLFLAGS_EXTERNAL;
            cl->stride     = 4; /* DWORDS */
            cl->data       = repEntry;
            cl->numAlloced = (repEntry->size + 3U) & ~3U;
            cl->numUsed    = cl->numAlloced ;

            RxClusterUnlock(cl);

            RxPacketDispatch(pk, 0, self);

            RWRETURN(TRUE);
        }

        RxPacketDestroy(pk, self);
    }

    RWRETURN(FALSE);
}

/****************************************************************************
 _rwSkyAtomicInstanceAnimation

 On entry   : Atomic
 On exit    :
 */

static void
_rwSkyAtomicInstanceAnimation(RpAtomic *apAtom)
{
    int                     batchVertexCount, batchTriangleCount;
    RwReal                  *instxyzw;
    RwReal                  *instuv;
    RwUInt32                *instrgb;
    RwInt8                  *instnorm;
    int                     instbl;
    RwResEntry              *repEntry = apAtom->repEntry;
    RwInstDataPolyHeader    *polyHeader = SKYPOLYHEADER(repEntry);
    RpGeometry              *geom = apAtom->geometry;
    RpMeshHeader            *meshHeader = geom->mesh;
    RpInterpolator          *interpolator = &apAtom->interpolator;
    RpMesh                  *mesh;
    const RWIMVERTEXINDEX   *meshIndices;
    RwInt32                 numMeshes, numTriangles;
    u_long128               *instData = (u_long128 *)polyHeader->vertices;

    RWFUNCTION(RWSTRING("_rwSkyAtomicInstanceAnimation"));
    PFENTRY(PF_rwSkyAtomicInstanceAnimation);

    if ((interpolator->startMorphTarget == interpolator->endMorphTarget) ||
        (interpolator->startMorphTarget >= geom->numMorphTargets) ||
        (interpolator->endMorphTarget >= geom->numMorphTargets))
    {
        /* Expand a single morph target */
        RwV3d              *vertices;
        RpMorphTarget      *morphTarget;

        if ((interpolator->startMorphTarget >= geom->numMorphTargets) ||
            (interpolator->endMorphTarget >= geom->numMorphTargets))
        {
            /* One of the morph target indices is invalid, we can't interpolate,
             * so just grab key frame zero, which is bound to exist.
             */
            morphTarget = &geom->morphTarget[0];
        }
        else
        {
            /* Both morph targets must be the same, so grab one or the other */
            morphTarget = &geom->morphTarget[interpolator->startMorphTarget];
        }

        vertices = morphTarget->verts;

        if (rwObjectTestFlags(geom, rpGEOMETRYNORMALS))
        {
            /* vertices & normals */
            RwV3d      *normals = morphTarget->normals;

            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);

            while (numMeshes--)
            {
                meshIndices = mesh->indices;
                numTriangles = mesh->numIndices/3;

                /* Skip dma tag and mask command */
                instData += 2;

                while (numTriangles)
                {
                    /* Start a new batch */
                    batchTriangleCount = numTriangles&~MAXVU1BATCHCNTMASK?MAXVU1BATCHCNT:numTriangles;
                    batchVertexCount = batchTriangleCount + (batchTriangleCount<<1);
                    numTriangles -= batchTriangleCount;

                    /* Skip DMA header */
                    instData++;

                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                    *(&instnorm) = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);
                
                    while (batchVertexCount--)
                    {
                        /* Process verts one by one */
                        RwUInt32 vert = (RwUInt32)(*meshIndices++);

                        /* Copy vertex positions */
                        *instxyzw++ = vertices[vert].x;
                        *instxyzw++ = vertices[vert].y;
                        *instxyzw++ = vertices[vert].z;

                        /* Transfer the normals */
                        *instnorm++ = (RwInt8)(normals[vert].x * (RwReal)(127.0f));
                        *instnorm++ = (RwInt8)(normals[vert].y * (RwReal)(127.0f));
                        *instnorm++ = (RwInt8)(normals[vert].z * (RwReal)(127.0f));
                    }

                    /* Now we know we are at the end of a batch */
                    while((int)instxyzw&0xf)
                    {
                        *instxyzw++ = 0.0f; /* I know this is 0x00000000 */
                    }
                    while((int)instnorm&0xf)
                    {
                        *instnorm++ = 0;
                    }

                    /* Skip vertices and execute command */
                    instData += instbl;
                }

#ifdef NOPSININST
                instData += 11+2;
#else /* NOPSININST */
                instData += 2;
#endif /* NOPSININST */

                /* Ready for next mesh */
                mesh++;
            }
        }
        else
        {
            /* vertices */
            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);

            while (numMeshes--)
            {
                meshIndices = mesh->indices;
                numTriangles = mesh->numIndices/3;

                /* Skip dmaTag and mask command */
                instData += 2;

                while (numTriangles)
                {
                    /* Start a new batch */
                    batchTriangleCount = numTriangles&~MAXVU1BATCHCNTMASK?MAXVU1BATCHCNT:numTriangles;
                    batchVertexCount = batchTriangleCount + (batchTriangleCount<<1);
                    numTriangles -= batchTriangleCount;

                    /* Skip DMA header */
                    instData++;

                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1) + 1);
                    *(&instnorm) = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2) + 1);
                
                    while (batchVertexCount--)
                    {
                        RwUInt32 vert = (RwUInt32)(*meshIndices++);

                        /* Copy vertex positions */
                        *instxyzw++ = vertices[vert].x;
                        *instxyzw++ = vertices[vert].y;
                        *instxyzw++ = vertices[vert].z;

                        /* Zero out the normals, we don't have any */
                        *instnorm++ = 0;
                        *instnorm++ = 0;
                        *instnorm++ = 0;
                    }

                    /* Now we know we are at the end of a batch */
                    while((int)instxyzw&0xf)
                    {
                        *instxyzw++ = 0.0f; /* I know this is 0x00000000 */
                    }
                    while((int)instnorm&0xf)
                    {
                        *instnorm++ = 0;
                    }

                    /* Skip vertices and execute command */
                    instData += instbl;
                }

#ifdef NOPSININST
                instData += 11+2;
#else /* NOPSININST */
                instData += 2;
#endif /* NOPSININST */

                /* Ready for next mesh */
                mesh++;
            }
        }
    }
    else
    {
        RpMorphTarget      *startMorphTarget = &geom->morphTarget[interpolator->startMorphTarget];
        RpMorphTarget      *endMorphTarget = &geom->morphTarget[interpolator->endMorphTarget];
        RwV3d              *startVerts = startMorphTarget->verts;
        RwV3d              *endVerts = endMorphTarget->verts;
        RwReal             scale = ((interpolator->recipTime) * ( interpolator->position));

        /* Have to interpolate to get values */
        if (startMorphTarget->normals)
        {
            /* vertices & normals */
            RwV3d *startNormals = startMorphTarget->normals;
            RwV3d *endNormals = endMorphTarget->normals;

            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);

            while (numMeshes--)
            {
                meshIndices = mesh->indices;
                numTriangles = mesh->numIndices/3;

                /* Skip DMAtag and mask command */
                instData += 2;

                while (numTriangles)
                {
                    /* Start a new batch */
                    batchTriangleCount = numTriangles&~MAXVU1BATCHCNTMASK?MAXVU1BATCHCNT:numTriangles;
                    batchVertexCount = batchTriangleCount + (batchTriangleCount<<1);
                    numTriangles -= batchTriangleCount;

                    /* Skip DMA header */
                    instData++;

                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                    *(&instnorm) = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);

                    while (batchVertexCount--)
                    {
                        RwV3d normal;
                        RwUInt32 vert = (RwUInt32)(*meshIndices++);

                        RwV3dSub((RwV3d*)instxyzw, &endVerts[vert], &startVerts[vert]);
                        RwV3dScale((RwV3d*)instxyzw, (RwV3d*)instxyzw, scale);
                        RwV3dAdd((RwV3d*)instxyzw, (RwV3d*)instxyzw, &startVerts[vert]);
                        instxyzw += 3; /* Step over vector */

                        /* Fill in the normals */
                        RwV3dSub(&normal, &endNormals[vert], &startNormals[vert]);
                        RwV3dScale(&normal, &normal, scale);
                        RwV3dAdd(&normal, &normal, &startNormals[vert]);
                        *instnorm++ = (RwInt8)(normal.x * (RwReal)(127.0f));
                        *instnorm++ = (RwInt8)(normal.y * (RwReal)(127.0f));
                        *instnorm++ = (RwInt8)(normal.z * (RwReal)(127.0f));
                    }

                    /* Now we know we are at the end of a batch */
                    while((int)instxyzw&0xf)
                    {
                        *instxyzw++ = 0.0f; /* I know this is 0x00000000 */
                    }
                    while((int)instnorm&0xf)
                    {
                        *instnorm++ = 0;
                    }

                    /* Skip vertices and execute command */
                    instData += instbl;
                }

#ifdef NOPSININST
                instData += 11+2;
#else /* NOPSININST */
                instData += 2;
#endif /* NOPSININST */

                /* Ready for next mesh */
                mesh++;
            }
        }
        else
        {
            /* vertices */

            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);

            while (numMeshes--)
            {
                meshIndices = mesh->indices;
                numTriangles = mesh->numIndices/3;

                /* Skip dma header and mask command */
                instData += 2;

                while (numTriangles)
                {
                    batchTriangleCount = numTriangles&~MAXVU1BATCHCNTMASK?MAXVU1BATCHCNT:numTriangles;
                    batchVertexCount = batchTriangleCount + (batchTriangleCount<<1);
                    numTriangles -= batchTriangleCount;

                    /* Skip DMA header */
                    instData++;

                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                    *(&instnorm) = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);
                
                    while (batchVertexCount--)
                    {
                        RwUInt32 vert = (RwUInt32)(*meshIndices++);

                        RwV3dSub((RwV3d*)instxyzw, &endVerts[vert], &startVerts[vert]);
                        RwV3dScale((RwV3d*)instxyzw, (RwV3d*)instxyzw, scale);
                        RwV3dAdd((RwV3d*)instxyzw, (RwV3d*)instxyzw, &startVerts[vert]);
                        instxyzw += 3;

                        /* Zero the normals (we have none) */
                        *instnorm++ = 0;
                        *instnorm++ = 0;
                        *instnorm++ = 0;
                    }

                    /* Now we know we are at the end of a batch */
                    while((int)instxyzw&0xf)
                    {
                        *instxyzw++ = 0.0f; /* I know this is 0x00000000 */
                    }
                    while((int)instnorm&0xf)
                    {
                        *instnorm++ = 0;
                    }

                    /* Skip vertices and execute command */
                    instData += instbl;
                }

#ifdef NOPSININST
                instData += 11+2;
#else /* NOPSININST */
                instData += 2;
#endif /* NOPSININST */

                /* Ready for next mesh */
                mesh++;
            }
        }
    }

    PFEXIT(PF_rwSkyAtomicInstanceAnimation);
    RWRETURNVOID();
}

/****************************************************************************
 _rwSkyAtomicInstance

 On entry   : Atomic sector
 On exit    : TRUE on success
 */

static RwResEntry *
_rwSkyAtomicInstance(void *object)
{
    RpAtomic                *apAtom = (RpAtomic *)object;
    RpGeometry              *geom = apAtom->geometry;
    int                     batchVertexCount, batchTriangleCount;
    int                     debugVertSize;
    RwReal                  *instxyzw;
    RwReal                  *instuv;
    RwUInt32                *instrgb;
    RwInt8                  *instnorm;
    int                     instbl;

    RWFUNCTION(RWSTRING("_rwSkyAtomicInstance"));
    PFENTRY(PF_rwSkyAtomicInstance);
    RWASSERT(geom);

    /* Don't instance anything with no geometry! */
    if (geom)
    {
        RpMeshHeader            *meshHeader = geom->mesh;
        RwInt32                 numVerts = geom->numTriangles * 3;
        RpInterpolator          *interpolator = &apAtom->interpolator;
        RwInstDataPolyHeader    *polyHeader;
        RwUInt16                overloadFlags = rwObjectGetFlags(geom) & ATOMICRENDERTYPEMASK;
        RwUInt16                needToReInstance = 0;
        const RWIMVERTEXINDEX   *meshIndices;
        RpMesh                  *mesh;
        RwInt32                 numMeshes, numTriangles;
        RwResEntry              *repEntry = apAtom->repEntry;
        u_long128               *instData;

        if (repEntry)
        {
            /* Poly header (because this particular implementation uses polygons) */
            polyHeader = SKYPOLYHEADER(repEntry);

            /* Do we need to re-instance - has the app changed the mesh or do
             * the vertices have different fields.
             */
            if (((polyHeader->serialNum & 0xFFFF) != meshHeader->serialNum) ||
                (polyHeader->overloadFlags != overloadFlags))
            {
                /* Release resources to force re-instance (this does vertices/normals too) */
                PFCALL(PF_rwSkyAtomicInstance);
                RwResourcesFreeResEntry(repEntry);
                PFRET(PF_rwSkyAtomicInstance);
                repEntry = NULL;
            }
            else
            {
                /* Force instancing of just what is needed */
                needToReInstance = geom->lockedSinceLastInst;
            }
        }

        if (!repEntry)
        {
            RwInt32                 sizeVert;
            RwInt32                 size;

            /* I need to run down the meshes here. Ugly */
            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);
            sizeVert = 0;
            while (numMeshes--)
            {
                numTriangles = mesh->numIndices/3;

                sizeVert += 32                                          /* Initial dmaHeader & maskcmd128 */
                            + (((3*3*numTriangles+3)>>2)<<4)            /* xyzw */
                            + (((3*numTriangles+1)>>1)<<4)              /* uv */
                            + (((3*numTriangles+3)>>2)<<(4))            /* rgba */
                            + (numTriangles>>MAXVU1BATCHCNTSHIFT)*(((MAXVU1BATCHCNT*3*3)+15)&~0xf) + (((3*3)*(numTriangles&(MAXVU1BATCHCNT-1))+15)&~0xf) /* normals */
                            + (((numTriangles+MAXVU1BATCHCNT-1)>>MAXVU1BATCHCNTSHIFT)<<4)*6 /* VIF commands */
#ifdef NOPSININST
                            + 11 * sizeof(u_long128)
#endif /* NOPSININST */
                            + 2 * sizeof(u_long128);

                mesh++;
            }
            debugVertSize = sizeVert;
            size = sizeof(SkyInstDataExt) + sizeof(RwInstDataPolyHeader) +
                   sizeof(RwMatrix) + sizeVert + 64;

            PFCALL(PF_rwSkyAtomicInstance);
            if (!RwResourcesAllocateResEntry(apAtom, &apAtom->repEntry, size, _rwSkyInstanceDestroyCallback))
            {
                 PFRET(PF_rwSkyAtomicInstance);
                 PFEXIT(PF_rwSkyAtomicInstance);
                 RWRETURN(NULL);
            }
            PFRET(PF_rwSkyAtomicInstance);

            /* Convenience access */
            repEntry = apAtom->repEntry;

            /* Set refcount to be 0 */
            *SKYINSTANCEREFCNT(repEntry) = 0;
            *SKYINSTANCECLRCNT(repEntry) = 0;

            /* Poly header (because this particular implementation uses polygons) */
            polyHeader = SKYPOLYHEADER(repEntry);

            /* Set up the pointers to the actual data ! */
            polyHeader->xForm = (void *)(polyHeader + 1);

            polyHeader->vertices = (void *)(((RwMatrix *)polyHeader->xForm)+1);
            /* Align the vertices on a cache line */
            polyHeader->vertices = (void *)(((RwUInt32)polyHeader->vertices + 63) & ~63);

            /* Cache the size */
            SKYINSTANCEEND(repEntry) = ((RwUInt8 *)polyHeader->vertices) + sizeVert;

            polyHeader->numVertices = numVerts;
            polyHeader->sizeOfVertex = 0;  /* This doesn't make a whole lot of sense with a SCATTER build */

            polyHeader->polygons = NULL;
            polyHeader->numPolygons = 0;
            polyHeader->sizeOfPolygon = 0;
            polyHeader->firstPolygonOffset = 0;

            polyHeader->overloadFlags = overloadFlags;
            polyHeader->mesh = (void *)meshHeader;

            /* Copy serial num so we know if the app changes things */
            polyHeader->serialNum = meshHeader->serialNum;

            /* Make sure we can copy the flags across - need
             * commonality between the flag sets
             */
            RWASSERT(rpGEOMETRYTEXTURED == rpWORLDTEXTURED);
            RWASSERT(rpGEOMETRYPRELIT == rpWORLDPRELIT);
            RWASSERT(rpGEOMETRYNORMALS == rpWORLDNORMALS);
            RWASSERT(rpGEOMETRYLIGHT == rpWORLDLIGHT);
            polyHeader->flags = RpGeometryGetFlags(geom);

            /* Bang in all the DMA and GIF tags */
            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);
            instData = (u_long128 *)polyHeader->vertices;

            while (numMeshes--)
            {
                u_long128               runCmd;

                numTriangles = mesh->numIndices/3;

                /* Ready for next mesh */
                mesh++;

                *instData++ = dma1Header128;
                *instData++ = maskCmd128;

                runCmd = runCmd128;

                while (numTriangles)
                {
                    /* Start a new batch */
                    batchTriangleCount = numTriangles&~MAXVU1BATCHCNTMASK?MAXVU1BATCHCNT:numTriangles;
                    batchVertexCount = batchTriangleCount + (batchTriangleCount<<1);
                    numTriangles -= batchTriangleCount;

                    *instData = dma1Header128;
                    /* length + 5 to include vif cmds */
                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;
                    *((RwUInt16*)instData) = instbl;
                    instData++;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                    instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);
                
                    /* Add VIF tags */

                    /* xyzw (and load ITOPS) (and mode = 0)*/
                    *((RwUInt64*)instxyzw-2) = (1l<<24)|(1<<8)|(4) | (0x05l<<(24+32));
                    *((RwUInt64*)instxyzw-1) = (((0x68l<<24) | (batchVertexCount<<16) | 0x8000l)<<32) |
                                               (4l<<24) | batchTriangleCount;

                    /* uv */
                    *((RwUInt64*)instuv-2) = (0x20l<<24) | (0xf0f0f0f0l<<32);
                    *((RwUInt64*)instuv-1) = ((0x74l<<24)|(batchVertexCount<<16) | 0x8001l)<<32;
                    *((RwUInt64*)instrgb-4) = 0;
                    *((RwUInt64*)instrgb-3) = 0;

                    /* rgba */
                    *((RwUInt64*)instrgb-2) = 0;
                    *((RwUInt64*)instrgb-1) = ((0x6el<<24)|(batchVertexCount<<16) | 0xc002l)<<32;

                    /* normals */
                    *((RwUInt64*)instnorm-2) = (0x20l<<24) | (0xc0c0c0c0l<<32);
                    *((RwUInt64*)instnorm-1) = ((0x7al<<24)|(batchVertexCount<<16) | 0x8003l)<<32;

                    /* Skip to the end of the batch */
                    instData += (instbl-1);

                    /* Put in the execute command */
                    *instData++ = runCmd;

                    /* Next time use a continue */
                    runCmd = contCmd128;
                }

#ifdef NOPSININST
                *instData++ = dma10Header128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
#endif /* NOPSININST */

                *instData++ = dma1Ret128;
                *instData++ = unmaskCmd128;
            }

            /* Need to ReInstance everything (we use the lock flag to see what's changed) */
            needToReInstance = rpGEOMETRYLOCKALL;
        }
        else
        {
            PFCALL(PF_rwSkyAtomicInstance);
            RwResourcesUseResEntry(apAtom->repEntry);
            PFRET(PF_rwSkyAtomicInstance);
        }

        if ((needToReInstance & (rpGEOMETRYLOCKTEXCOORDS|rpGEOMETRYLOCKPOLYGONS)) && rwObjectTestFlags(geom, rpGEOMETRYTEXTURED))
        {
            RwTexCoords      *vertexTexCoords = geom->vertexTexCoords;

            instData = (u_long128 *)polyHeader->vertices;
            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);

            /* Instance vertex texture coordinates */
            while (numMeshes--)
            {
                meshIndices = mesh->indices;
                numTriangles = mesh->numIndices/3;

                /* Jump over dma1Header and mask command */
                instData += 2;

                while (numTriangles)
                {
                    /* Start a new batch */
                    batchTriangleCount = numTriangles&~MAXVU1BATCHCNTMASK?MAXVU1BATCHCNT:numTriangles;
                    batchVertexCount = batchTriangleCount + (batchTriangleCount<<1);
                    numTriangles -= batchTriangleCount;

                    /* Jump over dmaHeader */
                    instData++;

                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                    instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);

                    /* Must process these as triangles to do the minUV thing */
                    while (batchTriangleCount--)
                    {
                        RwTexCoords tc1, tc2, tc3, minTc;

                        tc1 = vertexTexCoords[*meshIndices++];
                        tc2 = vertexTexCoords[*meshIndices++];
                        tc3 = vertexTexCoords[*meshIndices++];

                        minTc = tc1;
                        if (tc2.u < minTc.u)
                        {
                            minTc.u = tc2.u;
                        }
                        if (tc3.u < minTc.u)
                        {
                            minTc.u = tc3.u;
                        }
                        if (tc2.v < minTc.v)
                        {
                            minTc.v = tc2.v;
                        }
                        if (tc3.v < minTc.v)
                        {
                            minTc.v = tc3.v;
                        }

                        minTc.u = (RwReal)((RwUInt32)minTc.u);
                        minTc.v = (RwReal)((RwUInt32)minTc.v);

                        *instuv++ = tc1.u - minTc.u;
                        *instuv++ = tc1.v - minTc.v;

                        *instuv++ = tc2.u - minTc.u;
                        *instuv++ = tc2.v - minTc.v;

                        *instuv++ = tc3.u - minTc.u;
                        *instuv++ = tc3.v - minTc.v;
                    }

                    /* Now we know we 3re at the end of a batch */
                    while((int)instuv&0xf)
                    {
                        *(int*)instuv++ = 0;
                    }

                    /* Jump over vertices and execute command */
                    instData += instbl;
                }

#ifdef NOPSININST
                instData += 11+2;
#else /* NOPSININST */
                instData += 2;
#endif /* NOPSININST */

                /* Ready for next mesh */
                mesh++;
            }
        }

        if (needToReInstance & (rpGEOMETRYLOCKPRELIGHT|rpGEOMETRYLOCKPOLYGONS))
        {
            instData = (u_long128 *)polyHeader->vertices;
            /* Initialise preLitLum */
            if (rwObjectTestFlags(geom, rpGEOMETRYPRELIT))
            {
                RwRGBA     *preLitLum = geom->preLitLum;

                numMeshes = meshHeader->numMeshes;
                mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                                  meshHeader->firstMeshOffset);

                while (numMeshes--)
                {
                    meshIndices = mesh->indices;
                    numTriangles = mesh->numIndices/3;

                    /* Jump over dma1Header and mask command */
                    instData += 2;

                    while (numTriangles)
                    {
                        batchTriangleCount = numTriangles&~MAXVU1BATCHCNTMASK?MAXVU1BATCHCNT:numTriangles;
                        batchVertexCount = batchTriangleCount + (batchTriangleCount<<1);
                        numTriangles -= batchTriangleCount;

                        /* Jump over dmaHeader */
                        instData++;

                        instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                                 ((batchVertexCount+1)>>1) +
                                 ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                                 5;

                        instxyzw = (RwReal*)(instData+1);
                        instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                        instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                        instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);

                        while (batchVertexCount--)
                        {
#ifndef OVERRIDELIGHT
                            const RwRGBA  *col;

                            col = &preLitLum[*meshIndices++];

                            *instrgb++ = (RwUInt32)((RwUInt8)col->red)
                                         |((RwUInt32)((RwUInt8)col->green)<<8)
                                         |((RwUInt32)((RwUInt8)col->blue)<<16)
                                         |0xff000000;
#else /* !OVERRIDELIGHT */
                            *instrgb++ = 0xffffffff;
#endif /* !OVERRIDELIGHT */
                        }

                        /* Now we know we are at the end of a batch */
                        while ((int)instrgb&0xf)
                        {
                            *instrgb++ = 0;
                        }

                        /* Jump over vertices and execute command */
                        instData += instbl;
                    }

#ifdef NOPSININST
                    instData += 11+2;
#else /* NOPSININST */
                    instData += 2;
#endif /* NOPSININST */

                    /* Ready for next mesh */
                    mesh++;
                }
            }
            else
            {
                numMeshes = meshHeader->numMeshes;
                mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                                  meshHeader->firstMeshOffset);

                while (numMeshes--)
                {
                    numTriangles = mesh->numIndices/3;

                    /* Jump over dma1Header and mask command */
                    instData += 2;

                    while (numTriangles)
                    {
                        /* Start a new batch */
                        batchTriangleCount = numTriangles&~MAXVU1BATCHCNTMASK?MAXVU1BATCHCNT:numTriangles;
                        batchVertexCount = batchTriangleCount + (batchTriangleCount<<1);
                        numTriangles -= batchTriangleCount;

                        /* Jump over dmaHeader */
                        instData++;

                        instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                                 ((batchVertexCount+1)>>1) +
                                 ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                                 5;

                        instxyzw = (RwReal*)(instData+1);
                        instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                        instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                        instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);

                        while (batchVertexCount--)
                        {
#ifndef SUBSISTLIGHT
                            *instrgb++ = 0xff000000;
#else  /* !SUBSISTLIGHT */
                            *instrgb++ = 0xffffffff;
#endif  /* !SUBSISTLIGHT */
                        }

                        /* Now we know we are at the end of a batch */
                        while ((int)instrgb&0xf)
                        {
                            *instrgb++ = 0;
                        }

                        /* Jump over vertices and execute command */
                        instData += instbl;
                    }

#ifdef NOPSININST
                    instData += 11+2;
#else /* NOPSININST */
                    instData += 2;
#endif /* NOPSININST */

                    /* Ready for next mesh */
                    mesh++;
                }
            }
        }

        /* Do any animation */
        if ((interpolator->flags & (RwInt32)rpINTERPOLATORDIRTYINSTANCE) ||
            (needToReInstance & (rpGEOMETRYLOCKVERTICES | rpGEOMETRYLOCKNORMALS | rpGEOMETRYLOCKPOLYGONS)))
        {
            PFCALL(PF_rwSkyAtomicInstance);
            _rwSkyAtomicInstanceAnimation(apAtom);
            PFRET(PF_rwSkyAtomicInstance);

            /* Its no longer dirty */
            interpolator->flags &= ~rpINTERPOLATORDIRTYINSTANCE;
        }

        if (needToReInstance)
        {
            PFCALL(PF_rwSkyAtomicInstance);
            SyncDCache(polyHeader->vertices, SCESYNCDCACHEROUNDUP(SKYINSTANCEEND(repEntry)));
            PFRET(PF_rwSkyAtomicInstance);
        }

        /* All has been dealt with now */
        geom->lockedSinceLastInst = 0;

        /* Initialise local to camera matrix */
        {
            RwMatrix *localToCamera;
            RwMatrix *mpLocalToWorld;

            localToCamera = (RwMatrix *)polyHeader->xForm;
            PFCALL(PF_rwSkyAtomicInstance);
            mpLocalToWorld = RwFrameGetLTM((RwFrame *)rwObjectGetParent(apAtom));

            RwMatrixMultiply(localToCamera, mpLocalToWorld, &(((RwCamera *)RWSRCGLOBAL(curCamera))->viewMatrix));
            PFRET(PF_rwSkyAtomicInstance);
        }

        /* Do large frustum test to see if we need to clip/cull */
        {
            RwFrustumPlane     *frustPlane;
            const RwSphere     *sphere;
            RwUInt32           numPlanes;

            *SKYINSTANCEINFRUSTUM(apAtom->repEntry) = rwSPHEREINSIDE;
            
            sphere = rpAtomicCalcWorldBoundingSphere(apAtom);
            RWASSERT(sphere);

            frustPlane = CAMERAEXTFROMCAMERA(RWSRCGLOBAL(curCamera))->largeFrustumPlanes;
            numPlanes = 6;
            while (numPlanes--)
            {
                RwReal dot;

                dot = RwV3dDotProduct(&sphere->center, &frustPlane->plane.normal);
                dot -= frustPlane->plane.distance;

                /* We only need to detect boundary case, we should never get a
                 * totally outside.
                 */
                if (dot > -sphere->radius)
                {
                    *SKYINSTANCEINFRUSTUM(apAtom->repEntry) = rwSPHEREBOUNDARY;
                    break;
                }
                frustPlane++;
            }
        }

        /* Nothing done in the pipe yet */
        polyHeader->pipelineOpFlags = 0;

#if 0
{
/* Dump pkt we just built */
    int i;
    char buf[1024];
    RwUInt32* inst = polyHeader->vertices;

scePrintf("Atomic: pkt\n");
    for (i=0; i<debugVertSize; i+=16)
    {
        sprintf(buf, "%x %.8x_%.8x_%.8x_%.8x\n", inst, *(inst+3), *(inst+2), *(inst+1), *(inst));
        scePrintf(buf);
        inst+=4;
    }
scePrintf("\n");
}
#endif

        PFEXIT(PF_rwSkyAtomicInstance);
        RWRETURN(apAtom->repEntry);
    }

    PFEXIT(PF_rwSkyAtomicInstance);
    RWRETURN(NULL);
}

/****************************************************************************
 _rwSkyTSAtomicInstanceAnimation

 On entry   : Atomic
 On exit    :
 */

static void
_rwSkyTSAtomicInstanceAnimation(RpAtomic *apAtom)
{
    int batchVertexCount, batchTriangleCount;
    RwReal *instxyzw;
    RwReal *instuv;
    RwUInt32 *instrgb;
    RwInt8 *instnorm;
    int instbl;
    RwResEntry              *repEntry = apAtom->repEntry;
    RwInstDataPolyHeader    *polyHeader = SKYPOLYHEADER(repEntry);
    RpGeometry              *geom = apAtom->geometry;
    RpMeshHeader            *meshHeader = geom->mesh;
    RpInterpolator          *interpolator = &apAtom->interpolator;
    const RWIMVERTEXINDEX   *meshIndices;
    RpMesh                  *mesh;
    RwInt32                 numMeshes, numTriangles;
    u_long128               *instData = (u_long128 *)polyHeader->vertices;

    RWFUNCTION(RWSTRING("_rwSkyTSAtomicInstanceAnimation"));
    PFENTRY(PF_rwSkyTSAtomicInstanceAnimation);

    if ((interpolator->startMorphTarget == interpolator->endMorphTarget) ||
        (interpolator->startMorphTarget >= geom->numMorphTargets) ||
        (interpolator->endMorphTarget >= geom->numMorphTargets))
    {
        /* Expand a single morph target */
        RwV3d              *vertices;
        RpMorphTarget      *morphTarget;

        if ((interpolator->startMorphTarget >= geom->numMorphTargets) ||
            (interpolator->endMorphTarget >= geom->numMorphTargets))
        {
            /* One of the morph target indices is invalid, we can't interpolate,
             * so just grab key frame zero, which is bound to exist.
             */
            morphTarget = &geom->morphTarget[0];
        }
        else
        {
            /* Both morph targets must be the same, so grab one or the other */
            morphTarget = &geom->morphTarget[interpolator->startMorphTarget];
        }

        vertices = morphTarget->verts;

        if (rwObjectTestFlags(geom, rpGEOMETRYNORMALS))
        {
            /* vertices & normals */
            RwV3d      *normals = morphTarget->normals;

            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);
            while (numMeshes--)
            {
                meshIndices = mesh->indices;
                numTriangles = mesh->numIndices-2;

                /* Skip dMA header and mask cmd */
                instData += 2;

                while (numTriangles)
                {
                    /* Start a new batch */
                    batchTriangleCount = numTriangles>MAXTSLEN?MAXTSLEN:numTriangles;
                    batchVertexCount = batchTriangleCount+2;
                    numTriangles -= batchTriangleCount;

                    /* Skip dma header */
                    instData++;

                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                    instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);

                    /* Just do all the verts */
                    while (batchVertexCount--)
                    {

                        RwUInt32 vert = *meshIndices++;

                        *instxyzw++ = vertices[vert].x;
                        *instxyzw++ = vertices[vert].y;
                        *instxyzw++ = vertices[vert].z;

                        /* Fill in the normal */
                        *instnorm++ = (RwInt8)(normals[vert].x * (RwReal)(127.0f));
                        *instnorm++ = (RwInt8)(normals[vert].y * (RwReal)(127.0f));
                        *instnorm++ = (RwInt8)(normals[vert].z * (RwReal)(127.0f));
                    }

                    /* Back up the indices by 2 to start a new batch */
                    meshIndices -= 2;

                    /* Now we know we are at the end of a batch */
                    while((int)instxyzw&0xf)
                    {
                        *instxyzw++ = 0.0f; /* I know this 0x00000000 */
                    }
                    while((int)instnorm&0xf)
                    {
                        *instnorm++ = 0;
                    }
                    /* Skip vertices and execute command */
                    instData += instbl;
                }

#ifdef NOPSININST
                instData += 11+2;
#else /* NOPSININST */
                instData += 2;
#endif /* NOPSININST */

                /* Ready for next mesh */
                mesh++;
#ifdef DMAALIGN
                instData = (u_long128 *)(((RwUInt32)instData+DMAALIGN)&~DMAALIGN);
#endif /* DMAALIGN */
            }
        }
        else
        {
            /* vertices */
            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);

            while (numMeshes--)
            {
                meshIndices = mesh->indices;
                numTriangles = mesh->numIndices-2;

                /* Skip dma header and mask cmd */
                instData += 2;

                while (numTriangles)
                {
                    /* Start a batch */
                    batchTriangleCount = numTriangles>MAXTSLEN?MAXTSLEN:numTriangles;
                    batchVertexCount = batchTriangleCount+2;
                    numTriangles -= batchTriangleCount;

                    /* Skip dma header */
                    instData++;

                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                    instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);
                
                    /* Just do all the verts */
                    while (batchVertexCount--)
                    {
                        RwUInt32 vert = *meshIndices++;

                        *instxyzw++ = vertices[vert].x;
                        *instxyzw++ = vertices[vert].y;
                        *instxyzw++ = vertices[vert].z;

                        /* Zero the normal (we have none) */
                        *instnorm++ = 0;
                        *instnorm++ = 0;
                        *instnorm++ = 0;
                    }

                    /* Back up the indices by 2 to start a new batch */
                    meshIndices -= 2;

                    /* Now we know we are at the end of a batch */
                    while((int)instxyzw&0xf)
                    {
                        *instxyzw++ = 0.0f; /* I know this 0x00000000 */
                    }
                    while((int)instnorm&0xf)
                    {
                        *instnorm++ = 0;
                    }

                    /* Skip vertices and execute command */
                    instData += instbl;
                }

#ifdef NOPSININST
                instData += 11+2;
#else /* NOPSININST */
                instData += 2;
#endif /* NOPSININST */

                /* Ready for next mesh */
                mesh++;
#ifdef DMAALIGN
                instData = (u_long128*)(((RwUInt32)instData+DMAALIGN)&~DMAALIGN);
#endif /* DMAALIGN */
            }
        }
    }
    else
    {
        RpMorphTarget      *startMorphTarget = &geom->morphTarget[interpolator->startMorphTarget];
        RpMorphTarget      *endMorphTarget = &geom->morphTarget[interpolator->endMorphTarget];
        RwV3d              *startVerts = startMorphTarget->verts;
        RwV3d              *endVerts = endMorphTarget->verts;
        RwReal             scale = ((interpolator->recipTime) * ( interpolator->position));

        /* Have to interpolate to get values */
        if (startMorphTarget->normals)
        {
            /* vertices & normals */
            RwV3d *startNormals = startMorphTarget->normals;
            RwV3d *endNormals = endMorphTarget->normals;

            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);

            while (numMeshes--)
            {
                meshIndices = mesh->indices;
                numTriangles = mesh->numIndices-2;

                /* Skip DMA header and mask cmd */
                instData += 2;

                while (numTriangles)
                {
                    /* Start a new batch */
                    batchTriangleCount = numTriangles>MAXTSLEN?MAXTSLEN:numTriangles;
                    batchVertexCount = batchTriangleCount+2;
                    numTriangles -= batchTriangleCount;

                    /* Skip DMA tag */
                    instData++;

                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                    instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);
                
                    /* Just do all the verts */
                    while (batchVertexCount--)
                    {
                        RwV3d normal;
                        RwUInt32 vert = *meshIndices++;

                        RwV3dSub((RwV3d*)instxyzw, &endVerts[vert], &startVerts[vert]);
                        RwV3dScale((RwV3d*)instxyzw, (RwV3d*)instxyzw, scale);
                        RwV3dAdd((RwV3d*)instxyzw, (RwV3d*)instxyzw, &startVerts[vert]);
                        instxyzw += 3;

                        /* Fill in the normal */
                        RwV3dSub(&normal, &endNormals[vert], &startNormals[vert]);
                        RwV3dScale(&normal, &normal, scale);
                        RwV3dAdd(&normal, &normal, &startNormals[vert]);
                        *instnorm++ = (RwInt8)(normal.x * (RwReal)(127.0f));
                        *instnorm++ = (RwInt8)(normal.y * (RwReal)(127.0f));
                        *instnorm++ = (RwInt8)(normal.z * (RwReal)(127.0f));
                    }

                    /* Back up the indices by 2 to start a new batch */
                    meshIndices -= 2;

                    /* Now we know we are at the end of a batch */
                    while((int)instxyzw&0xf)
                    {
                        *instxyzw++ = 0.0f; /* I know this 0x00000000 */
                    }
                    while((int)instnorm&0xf)
                    {
                        *instnorm++ = 0;
                    }

                    /* Skip verts and exec cmd */
                    instData += instbl;
                }

#ifdef NOPSININST
                instData += 11+2;
#else /* NOPSININST */
                instData += 2;
#endif /* NOPSININST */

                /* Ready for next mesh */
                mesh++;
#ifdef DMAALIGN
                instData = (u_long128 *)(((RwUInt32)instData+DMAALIGN)&~DMAALIGN);
#endif /* DMAALIGN */
            }
        }
        else
        {
            /* vertices */

            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);

            while (numMeshes--)
            {
                meshIndices = mesh->indices;
                numTriangles = mesh->numIndices-2;

                /* Skip dma header and mask cmd */
                instData += 2;

                while (numTriangles)
                {
                    /* Start a new batch */
                    batchTriangleCount = numTriangles>MAXTSLEN?MAXTSLEN:numTriangles;
                    batchVertexCount = batchTriangleCount + 2;
                    numTriangles -= batchTriangleCount;

                    /* Skip dma header */
                    instData++;

                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                    instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);
                
                    /* Now do the rest of the batch */
                    while (batchVertexCount--)
                    {
                        RwUInt32 vert = *meshIndices++;

                        RwV3dSub((RwV3d*)instxyzw, &endVerts[vert], &startVerts[vert]);
                        RwV3dScale((RwV3d*)instxyzw, (RwV3d*)instxyzw, scale);
                        RwV3dAdd((RwV3d*)instxyzw, (RwV3d*)instxyzw, &startVerts[vert]);
                        instxyzw += 3;

                        /* Zero the normals (we have none) */
                        *instnorm++ = 0;
                        *instnorm++ = 0;
                        *instnorm++ = 0;
                    }

                    /* Back up the indices by 2 to start a new batch */
                    meshIndices -= 2;

                    /* Now we know we are at the end of a batch */
                    while((int)instxyzw&0xf)
                    {
                        *instxyzw++ = 0.0f; /* I know this 0x00000000 */
                    }
                    while((int)instnorm&0xf)
                    {
                        *instnorm++ = 0;
                    }

                    /* Skip verts and exec cmd */
                    instData += instbl;
                }

#ifdef NOPSININST
                instData += 11+2;
#else /* NOPSININST */
                instData += 2;
#endif /* NOPSININST */

                /* Ready for next mesh */
                mesh++;
#ifdef DMAALIGN
                instData = (u_long128 *)(((RwUInt32)instData+DMAALIGN)&~DMAALIGN);
#endif /* DMAALIGN */
            }
        }
    }

    PFEXIT(PF_rwSkyTSAtomicInstanceAnimation);
    RWRETURNVOID();
}

/****************************************************************************
 _rwSkyTSAtomicInstance

 On entry   : Atomic
 On exit    : TRUE on success
 */

static RwResEntry *
_rwSkyTSAtomicInstance(void *object)
{
    RpAtomic                *apAtom = (RpAtomic *)object;
    RpGeometry              *geom = apAtom->geometry;
    int                     batchVertexCount, batchTriangleCount;
    int                     debugVertSize;
    RwReal                  *instxyzw;
    RwReal                  *instuv;
    RwUInt32                *instrgb;
    RwInt8                  *instnorm;
    int                     instbl;

    RWFUNCTION(RWSTRING("_rwSkyTSAtomicInstance"));
    PFENTRY(PF_rwSkyTSAtomicInstance);
    RWASSERT(geom);

    /* Don't instance anything with no geometry! */
    if (geom)
    {
        RpMeshHeader            *meshHeader = geom->mesh;
        RwInt32                 numVerts = geom->numTriangles * 3;
        RpInterpolator          *interpolator = &apAtom->interpolator;
        RwInstDataPolyHeader    *polyHeader;
        RwUInt16                overloadFlags = rwObjectGetFlags(geom) & ATOMICRENDERTYPEMASK;
        RwUInt16                needToReInstance = 0;
        const RWIMVERTEXINDEX	*meshIndices;
        RpMesh                  *mesh;
        RwInt32                 numMeshes, numTriangles;
        RwResEntry              *repEntry = apAtom->repEntry;
        u_long128               *instData;

        if (repEntry)
        {
            /* Poly header (because this particular implementation uses polygons) */
            polyHeader = SKYPOLYHEADER(repEntry);

            /* Do we need to re-instance - has the app changed the mesh or do
             * the vertices have different fields.
             */
            if (((polyHeader->serialNum & 0xFFFF) != meshHeader->serialNum) ||
                (polyHeader->overloadFlags != overloadFlags))
            {
                /* Release resources to force re-instance (this does vertices/normals too) */
                PFCALL(PF_rwSkyTSAtomicInstance);
                RwResourcesFreeResEntry(repEntry);
                PFRET(PF_rwSkyTSAtomicInstance);
                repEntry = NULL;
            }
            else
            {
                /* Force instancing of just what is needed */
                needToReInstance = geom->lockedSinceLastInst;
            }
        }

        if (!repEntry)
        {
            RwInt32                 sizeVert;
            RwInt32                 size;

            /* I need to run down the meshes here. Ugly */
            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);
            sizeVert = 0;
            while (numMeshes--)
            {
                int vCount;
                numTriangles = mesh->numIndices-2;
                /* Vcount is numTriangles+((numTriangles+MAXTSLEN-1)/MAXTSLEN)*2 */

                vCount = numTriangles + ((numTriangles+MAXTSLEN-1)/MAXTSLEN)*2;

                sizeVert += 32                                  /* dmaHeader & mask command */
                            + (((vCount+(vCount<<1)+3)>>2)<<4)  /* xyzw */
                            + (((vCount+1)>>1)<<4)              /* uv */
                            + (((vCount+3)>>2)<<(4))            /* RGBA */
                            + (numTriangles/MAXTSLEN)*((((MAXTSLEN+2)*3)+15)&~0xf) + (((3)*(vCount%(MAXTSLEN+2))+15)&~0xf) /* normals */
                            + (((vCount+MAXTSVERTS-1)/MAXTSVERTS)<<4)*6 /* VIF commands */
#ifdef NOPSININST
                            + 11 * sizeof(u_long128)
#endif /* NOPSININST */
                            + 2 * sizeof(u_long128);

                mesh++;
#ifdef DMAALIGN
                sizeVert = (sizeVert+DMAALIGN)&~DMAALIGN;
#endif /* DMAALIGN */
            }
            debugVertSize = sizeVert;
            size = sizeof(SkyInstDataExt) + sizeof(RwInstDataPolyHeader) +
                   sizeof(RwMatrix) + sizeVert
#ifndef DMAALIGN
                   + 64;
#else /* !DMAALIGN */
#if (DMAALIGN > 63)
                   + (DMAALIGN+1);
#else /* (DMAALIGN > 63) */
                   + 64;
#endif /* (DMAALIGN > 63) */
#endif /* !DMAALIGN */

            PFCALL(PF_rwSkyTSAtomicInstance);
            if (!RwResourcesAllocateResEntry(apAtom, &apAtom->repEntry, size, _rwSkyInstanceDestroyCallback))
            {
                 PFRET(PF_rwSkyTSAtomicInstance);
                 PFEXIT(PF_rwSkyTSAtomicInstance);
                 RWRETURN(NULL);
            }
            PFRET(PF_rwSkyTSAtomicInstance);

            /* Convenience access */
            repEntry = apAtom->repEntry;

            /* Set refcount to be 0 */
            *SKYINSTANCEREFCNT(repEntry) = 0;
            *SKYINSTANCECLRCNT(repEntry) = 0;

            /* Poly header (because this particular implementation uses polygons) */
            polyHeader = SKYPOLYHEADER(repEntry);

            /* Set up the pointers to the actual data ! */
            polyHeader->xForm = (void *)(polyHeader + 1);

            polyHeader->vertices = (void *)(((RwMatrix *)polyHeader->xForm)+1);
            /* Align the vertices on a cache line */
#ifndef DMAALIGN
            polyHeader->vertices = (void *)(((RwUInt32)polyHeader->vertices + 63) & ~63);
#else /* !DMAALIGN */
#if (DMAALIGN > 63)
            polyHeader->vertices = (void *)(((RwUInt32)polyHeader->vertices + DMAALIGN) & ~DMAALIGN);
#else /* (DMAALIGN > 63) */
            polyHeader->vertices = (void *)(((RwUInt32)polyHeader->vertices + 63) & ~63);
#endif /* (DMAALIGN > 63) */
#endif /* !DMAALIGN */

            /* Cache the size */
            SKYINSTANCEEND(repEntry) = ((RwUInt8 *)polyHeader->vertices) + sizeVert;

            polyHeader->numVertices = numVerts;
            polyHeader->sizeOfVertex = 0; /* This doesn't make a whole lot of sense with a SCATTER build */

            polyHeader->polygons = NULL;
            polyHeader->numPolygons = 0;
            polyHeader->sizeOfPolygon = 0;
            polyHeader->firstPolygonOffset = 0;

            polyHeader->overloadFlags = overloadFlags;
            polyHeader->mesh = (void *)meshHeader;

            /* Copy serial num so we know if the app changes things */
            polyHeader->serialNum = meshHeader->serialNum;

            /* Make sure we can copy the flags across - need
             * commonality between the flag sets
             */
            RWASSERT(rpGEOMETRYTEXTURED == rpWORLDTEXTURED);
            RWASSERT(rpGEOMETRYPRELIT == rpWORLDPRELIT);
            RWASSERT(rpGEOMETRYNORMALS == rpWORLDNORMALS);
            RWASSERT(rpGEOMETRYLIGHT == rpWORLDLIGHT);
            polyHeader->flags = RpGeometryGetFlags(geom);

            /* Bang in the DMA and VIF tags */
            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);
            instData = (u_long128 *)polyHeader->vertices;

            while (numMeshes--)
            {
                u_long128               runCmd;

                numTriangles = mesh->numIndices-2;

                /* Ready for next mesh */
                mesh++;

                *instData++ = dma1Header128;
                *instData++ = maskCmd128;

                runCmd = runCmd128;

                while (numTriangles)
                {
                    /* Start a batch */
                    batchTriangleCount = numTriangles>MAXTSLEN?MAXTSLEN:numTriangles;
                    batchVertexCount = batchTriangleCount+2;
                    numTriangles -= batchTriangleCount;

                    *instData = dma1Header128;
                    /* length + 5 to include vif cmds */
                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;
                    *((RwUInt16*)instData) = instbl;
                    instData++;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                    instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);
                
                    /* Add VIF tags */

                    /* xyzw (and load ITOPS) (and mode = 0) */
                    *((RwUInt64*)instxyzw-2) = (1l<<24)|(1<<8)|(4) | (0x05l<<(24+32));
                    *((RwUInt64*)instxyzw-1) = (((0x68l<<24) | (batchVertexCount<<16) | 0x8000l)<<32) |
                                               (4l<<24) | batchVertexCount;

                    /* uv */
                    *((RwUInt64*)instuv-2) = (0x20l<<24) | (0xf0f0f0f0l<<32);
                    *((RwUInt64*)instuv-1) = ((0x74l<<24)|(batchVertexCount<<16) | 0x8001l)<<32;
                    *((RwUInt64*)instrgb-4) = 0;
                    *((RwUInt64*)instrgb-3) = 0;

                    /* rgba */
                    *((RwUInt64*)instrgb-2) = 0;
                    *((RwUInt64*)instrgb-1) = ((0x6el<<24)|(batchVertexCount<<16) | 0xc002l)<<32;

                    /* normals */
                    *((RwUInt64*)instnorm-2) = (0x20l<<24) | (0xc0c0c0c0l<<32);
                    *((RwUInt64*)instnorm-1) = ((0x7al<<24)|(batchVertexCount<<16) | 0x8003l)<<32;

                    /* Skip to the end of the batch */
                    instData += (instbl-1);

                    /* Put in the execute command */
                    *instData++ = runCmd;

                    /* Next time use a continue */
                    runCmd = contCmd128;
                }

#ifdef NOPSININST
                *instData++ = dma10Header128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
                *instData++ = zero128;
#endif /* NOPSININST */

                *instData++ = dma1Ret128;
                *instData++ = unmaskCmd128;

#ifdef DMAALIGN
                instData = (u_long128*)(((RwUInt32)instData+DMAALIGN)&~DMAALIGN);
#endif /* DMAALIGN */
            }

            /* Need to ReInstance everything (we use the lock flag to see what's changed) */
            needToReInstance = rpGEOMETRYLOCKALL;
        }
        else
        {
            PFCALL(PF_rwSkyTSAtomicInstance);
            RwResourcesUseResEntry(apAtom->repEntry);
            PFRET(PF_rwSkyTSAtomicInstance);
        }

        if ((needToReInstance & (rpGEOMETRYLOCKTEXCOORDS|rpGEOMETRYLOCKPOLYGONS)) && rwObjectTestFlags(geom, rpGEOMETRYTEXTURED))
        {
            RwTexCoords      *vertexTexCoords = geom->vertexTexCoords;

            instData = (u_long128 *)polyHeader->vertices;
            numMeshes = meshHeader->numMeshes;
            mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                              meshHeader->firstMeshOffset);

            /* Instance vertex texture coordinates */
            while (numMeshes--)
            {
                meshIndices = mesh->indices;
                numTriangles = mesh->numIndices-2;

                /* Skip over dmaHeader and mask command */
                instData += 2;

                while (numTriangles)
                {
                    /* Start new batch */
                    batchTriangleCount = numTriangles>MAXTSLEN?MAXTSLEN:numTriangles;
                    batchVertexCount = batchTriangleCount + 2;
                    numTriangles -= batchTriangleCount;

                    /* Skip over dmaHeader */
                    instData++;

                    instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                             ((batchVertexCount+1)>>1) +
                             ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                             5;

                    instxyzw = (RwReal*)(instData+1);
                    instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                    instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                    instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);

                    /* Now do the rest of the batch */
                    while (batchVertexCount--)
                    {
                        RwTexCoords *texCoords = &vertexTexCoords[*meshIndices++];

                        *instuv++ = texCoords->u;
                        *instuv++ = texCoords->v;
                    }

                    /* Back up the indices by 2 to start a new batch */
                    meshIndices -= 2;

                    /* Now we know we are at the end of a batch */
                    while((int)instuv&0xf)
                    {
                        *(int*)instuv++ = 0;
                    }

                    /* Skip vertices and execute command */
                    instData += instbl;
                }

#ifdef NOPSININST
                instData += 11+2;
#else /* NOPSININST */
                instData += 2;
#endif /* NOPSININST */

                /* Ready for next mesh */
                mesh++;
#ifdef DMAALIGN
                instData = (u_long128 *)(((RwUInt32)instData+DMAALIGN)&~DMAALIGN);
#endif /* DMAALIGN */
            }
        }

        if (needToReInstance & (rpGEOMETRYLOCKPRELIGHT|rpGEOMETRYLOCKPOLYGONS))
        {
            instData = (u_long128 *)polyHeader->vertices;
            /* Initialise preLitLum */
            if (rwObjectTestFlags(geom, rpGEOMETRYPRELIT))
            {
                RwRGBA     *preLitLum = geom->preLitLum;

                numMeshes = meshHeader->numMeshes;
                mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                                  meshHeader->firstMeshOffset);

                while (numMeshes--)
                {
                    meshIndices = mesh->indices;
                    numTriangles = mesh->numIndices-2;

                    /* Skip over dmaHeader and mask command */
                    instData += 2;

                    while (numTriangles)
                    {
                        /* Start new batch */
                        batchTriangleCount = numTriangles>MAXTSLEN?MAXTSLEN:numTriangles;
                        batchVertexCount = batchTriangleCount + 2;
                        numTriangles -= batchTriangleCount;

                        /* Skip over dmaHeader */
                        instData++;

                        instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                                 ((batchVertexCount+1)>>1) +
                                 ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                                 5;

                        instxyzw = (RwReal*)(instData+1);
                        instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                        instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                        instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);

                        /* Then do the rest of the batch */
                        while (batchVertexCount--)
                        {
#ifndef OVERRIDELIGHT
                            const RwRGBA *col;

                            col = &preLitLum[*meshIndices++];

                            *instrgb++ = (RwUInt32)((RwUInt8)col->red)
                                         |((RwUInt32)((RwUInt8)col->green)<<8)
                                         |((RwUInt32)((RwUInt8)col->blue)<<16)
                                         |0xff000000;
#else /* !OVERRIDELIGHT */
                            *instrgb++ = 0xffffffff;
#endif /* !OVERRIDELIGHT */
                        }

                        /* Back up the indices by 2 to start a new batch */
                        meshIndices -= 2;

                        /* Now we know we are at the end of a batch */
                        while ((int)instrgb&0xf)
                        {
                            *instrgb++ = 0;
                        }

                        /* Skip over vertices and execute command */
                        instData += instbl;
                    }
#ifdef NOPSININST
                    instData += 11+2;
#else /* NOPSININST */
                    instData += 2;
#endif /* NOPSININST */

                    /* Ready for next mesh */
                    mesh++;
#ifdef DMAALIGN
                    instData = (u_long128 *)(((RwUInt32)instData+DMAALIGN)&~DMAALIGN);
#endif /* DMAALIGN */
                }
            }
            else
            {
                numMeshes = meshHeader->numMeshes;
                mesh = (RpMesh *)((RwUInt8 *)(meshHeader + 1) +
                                  meshHeader->firstMeshOffset);

                while (numMeshes--)
                {
                    numTriangles = mesh->numIndices-2;

                    /* Skip over dmaHeader and mask command */
                    instData += 2;

                    while (numTriangles)
                    {
                        /* Start a new batch */
                        batchTriangleCount = numTriangles>MAXTSLEN?MAXTSLEN:numTriangles;
                        batchVertexCount = batchTriangleCount + 2;
                        numTriangles -= batchTriangleCount;

                        /* Skip over dmaHeader */
                        instData++;

                        instbl = ((batchVertexCount+(batchVertexCount<<1)+3)>>2) +
                                 ((batchVertexCount+1)>>1) +
                                 ((batchVertexCount+3)>>2) +
                             ((batchVertexCount+(batchVertexCount<<1)+15)>>4) +
                                 5;

                        instxyzw = (RwReal*)(instData+1);
                        instuv = (RwReal *)((u_long128*)instxyzw+((batchVertexCount+(batchVertexCount<<1)+3)>>2)+1);
                        instrgb = (RwUInt32 *)((u_long128*)instuv+((batchVertexCount+1)>>1)+1);
                        instnorm = (RwInt8 *)((u_long128*)instrgb+((batchVertexCount+3)>>2)+1);

                        /* Just do the batch as one big chunk (the first two vertices are not special */
                        while (batchVertexCount--)
                        {
#ifndef SUBSISTLIGHT
                           *instrgb++ = 0xff000000;
#else  /* !SUBSISTLIGHT */
                           *instrgb++ = 0xffffffff;
#endif  /* !SUBSISTLIGHT */
                        }

                        /* Back up the indices by 2 to start a new batch */
                        meshIndices -= 2;

                        /* Now we know we are at the end of a batch */
                        while ((int)instrgb&0xf)
                        {
                            *instrgb++ = 0;
                        }

                        /* Skip vertices and execute command */
                        instData += instbl;
                    }

#ifdef NOPSININST
                    instData += 11+2;
#else /* NOPSININST */
                    instData += 2;
#endif /* NOPSININST */

                    /* Ready for next mesh */
                    mesh++;
#ifdef DMAALIGN
                    instData = (u_long128 *)(((RwUInt32)instData+DMAALIGN)&~DMAALIGN);
#endif /* DMAALIGN */
                }
            }
        }

        /* Do any animation */
        if ((interpolator->flags & (RwInt32)rpINTERPOLATORDIRTYINSTANCE) ||
            (needToReInstance & (rpGEOMETRYLOCKVERTICES | rpGEOMETRYLOCKNORMALS | rpGEOMETRYLOCKPOLYGONS)))
        {
            PFCALL(PF_rwSkyTSAtomicInstance);
            _rwSkyTSAtomicInstanceAnimation(apAtom);
            PFRET(PF_rwSkyTSAtomicInstance);

            /* Its no longer dirty */
            interpolator->flags &= ~rpINTERPOLATORDIRTYINSTANCE;
        }

        if (needToReInstance)
        {
            PFCALL(PF_rwSkyTSAtomicInstance);
            SyncDCache(polyHeader->vertices, SCESYNCDCACHEROUNDUP(SKYINSTANCEEND(repEntry)));
            PFRET(PF_rwSkyTSAtomicInstance);
        }

        /* All has been dealt with now */
        geom->lockedSinceLastInst = 0;

        /* Initialise local to camera matrix */
        {
            RwMatrix *localToCamera;
            RwMatrix *mpLocalToWorld;

            localToCamera = (RwMatrix *)polyHeader->xForm;
            PFCALL(PF_rwSkyTSAtomicInstance);
            mpLocalToWorld = RwFrameGetLTM((RwFrame *)rwObjectGetParent(apAtom));

            RwMatrixMultiply(localToCamera, mpLocalToWorld, &(((RwCamera *)RWSRCGLOBAL(curCamera))->viewMatrix));
            PFRET(PF_rwSkyTSAtomicInstance);
        }

        /* Do large frustum test to see if we need to clip/cull */
        {
            RwFrustumPlane     *frustPlane;
            const RwSphere     *sphere;
            RwUInt32           numPlanes;

            *SKYINSTANCEINFRUSTUM(apAtom->repEntry) = rwSPHEREINSIDE;
            
            sphere = rpAtomicCalcWorldBoundingSphere(apAtom);
            RWASSERT(sphere);

            frustPlane = CAMERAEXTFROMCAMERA(RWSRCGLOBAL(curCamera))->largeFrustumPlanes;
            numPlanes = 6;
            while (numPlanes--)
            {
                RwReal dot;

                dot = RwV3dDotProduct(&sphere->center, &frustPlane->plane.normal);
                dot -= frustPlane->plane.distance;

                /* We only need to detect boundary case, we should never get a
                 * totally outside.
                 */
                if (dot > -sphere->radius)
                {
                    *SKYINSTANCEINFRUSTUM(apAtom->repEntry) = rwSPHEREBOUNDARY;
                    break;
                }
                frustPlane++;
            }
        }

        /* Nothing done in the pipe yet */
        polyHeader->pipelineOpFlags = 0;

#if 0
{
/* Dump pkt we just built */
    int i;
    RwUInt32* inst = polyHeader->vertices;

printf("Atomic: pkt\n");
    for (i=0; i<debugVertSize; i+=16)
    {
        printf("%x %.8x_%.8x_%.8x_%.8x\n", inst, *(inst+3), *(inst+2), *(inst+1), *(inst));
        inst+=4;
    }
printf("\n");
}
#endif

        PFEXIT(PF_rwSkyTSAtomicInstance);
        RWRETURN(apAtom->repEntry);
    }

    PFEXIT(PF_rwSkyTSAtomicInstance);
    RWRETURN(NULL);
}

/****************************************************************************
 _rwPS2AtomicInstanceNodeBody()
 */

static RwBool
_rwPS2AtomicInstanceNodeBody(RxPipelineNodeInstance *self,
                             RxMemoryArena *arena __RWUNUSED__,
                             void *data)
{
    RpAtomic        *apAtom = (RpAtomic *)data;
    RpGeometry      *geom = apAtom->geometry;
    RwResEntry      *repEntry = NULL;

    RWFUNCTION(RWSTRING("_rwPS2AtomicInstanceNodeBody"));
    PFENTRY(PF_rwSkyAtomicSelectInstance);
    RWASSERT(data);

    if (geom)
    {
        RpMeshHeader            *meshHeader = geom->mesh;

#ifdef RWMETRICS
        RWSRCGLOBAL(metrics)->numVertices += geom->numVertices;
        RWSRCGLOBAL(metrics)->numTriangles += geom->numTriangles;
        if (meshHeader->flags & rpMESHHEADERTRISTRIP)
        {
            RWSRCGLOBAL(metrics)->numProcTriangles +=
                meshHeader->totalIndicesInMesh - (2 * meshHeader->numMeshes);
        }
        else
        {
            RWSRCGLOBAL(metrics)->numProcTriangles +=
                meshHeader->totalIndicesInMesh / 3;
        }
#endif /* RWMETRICS */

#ifdef CHECKSPHERES
        PFCALL(PF_rwSkyAtomicSelectInstance);
        _checkGeometrySpheres(geom);
        PFRET(PF_rwSkyAtomicSelectInstance);
#endif /* CHECKSPHERES */

#ifdef TSATOMIC
        PFCALL(PF_rwSkyAtomicSelectInstance);
        repEntry = _rwSkyTSAtomicInstance(data);
        PFRET(PF_rwSkyAtomicSelectInstance);
#else /* TSATOMIC */
        if (meshHeader->flags & rpMESHHEADERTRISTRIP)
        {
            PFCALL(PF_rwSkyAtomicSelectInstance);
            repEntry = _rwSkyTSAtomicInstance(data);
            PFRET(PF_rwSkyAtomicSelectInstance);
        }
        else
        {
            PFCALL(PF_rwSkyAtomicSelectInstance);
            repEntry = _rwSkyAtomicInstance(data);
            PFRET(PF_rwSkyAtomicSelectInstance);
        }
#endif /* TSATOMIC */
    }

    if ( repEntry != NULL &&
           _rwQueuePS2DMAChainPacket(self, repEntry) )
    {
        PFEXIT(PF_rwSkyWorldSectorInstance);
        RWRETURN(TRUE);
    }

    PFEXIT(PF_rwSkyAtomicSelectInstance);
    RWRETURN(FALSE);
}

/************************************************************************
 * _func <f RxNodeDefinitionGetPS2AtomicInstance> <f todo>
 *
 * _rdesc a pointer to a node to <f todo>
 *
 * _xref <f RxPipelineNodePS2MatInstanceGenerateCluster>
 * _xref <f RxNodeDefinitionGetPS2Dispatch>
 * _xref <f RxNodeDefinitionGetPS2Light>
 * _xref <f RxNodeDefinitionGetPS2MatBridge>
 * _xref <f RxNodeDefinitionGetPS2MatInstance>
 * _xref <f RxNodeDefinitionGetPS2ObjClose>
 * _xref <f RxNodeDefinitionGetPS2ObjEnumMeshes>
 * _xref <f RxNodeDefinitionGetPS2ObjOpen>
 * _xref <f RxNodeDefinitionGetPS2WorldSectorInstance>
 */
RxNodeDefinition *
RxNodeDefinitionGetPS2AtomicInstance(void)
{

    static RxClusterRef gNodeClusters[] = 
    {
        /* 0 */
        { &RxClPS2DMAChain, rxCLALLOWABSENT, rxCLRESERVED }
    };

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

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

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

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

    static RxOutputSpec gNodeOuts[] =
    {
        {
            _DefaultOutput,             // Name
            gNodeOut1,                   // OutputClusters
            rxCLVALID_INVALID             // AllOtherClusters
        }
    };

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

    static RwChar _PS2AtomicInstance_csl[] = RWSTRING("PS2AtomicInstance.csl");

    static RxNodeDefinition nodePS2AtomicInstanceCSL =
    {
        _PS2AtomicInstance_csl,               // Name
        {                                      // nodemethods
            _rwPS2AtomicInstanceNodeBody,      // +-- nodebody
            NULL,
            NULL,
            NULL,
            NULL,
            NULL,
            NULL
        },
        {                                      // Io
            NUMCLUSTERSOFINTEREST,             // +-- NumClustersOfInterest
            gNodeClusters,                     // +-- ClustersOfInterest
            gNodeReqs,                         // +-- InputRequirements
            NUMOUTPUTS,                        // +-- NumOutputs
            gNodeOuts                          // +-- Outputs
        },
        0,
        FALSE,
        0
    };

    RxNodeDefinition *result = &nodePS2AtomicInstanceCSL;
    
    RWAPIFUNCTION(RWSTRING("RxNodeDefinitionGetPS2AtomicInstance"));

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

    RWRETURN(result);

}

