/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#include "hsTypes.h"

#include "plParticleFiller.h"

// Core background
#include "hsTemplates.h"
#include "hsFastMath.h"
#include "plPipeline.h"
#include "plViewTransform.h"

// Getting at the destination data
#include "pnSceneObject/plDrawInterface.h"
#include "plDrawable/plDrawableSpans.h"
#include "plPipeline/plGBufferGroup.h"

// For shading
#include "plGLight/plLightInfo.h"

// Getting at the source data
#include "plParticleSystem/plParticleEmitter.h"
#include "plParticleSystem/plParticle.h"

static hsScalar sInvDelSecs;

//// Local Static Stuff ///////////////////////////////////////////////////////

/// Macros for getting/setting data in a D3D vertex buffer
#define STUFF_POINT( ptr, point ) { float *fPtr = (float *)ptr; \
                                    fPtr[ 0 ] = point.fX; fPtr[ 1 ] = point.fY; fPtr[ 2 ] = point.fZ; \
                                    ptr += sizeof( float ) * 3; }

#define STUFF_UINT32( ptr, uint ) { UInt32 *dPtr = (UInt32 *)ptr; \
                                    dPtr[ 0 ] = uint; ptr += sizeof( UInt32 ); }

#define EXTRACT_POINT( ptr, pt ) { float *fPtr = (float *)ptr; \
                                      pt.fX = fPtr[ 0 ]; pt.fY = fPtr[ 1 ]; pt.fZ = fPtr[ 2 ]; \
                                      ptr += sizeof( float ) * 3; }
#define EXTRACT_FLOAT( ptr, f ) { float *fPtr = (float *)ptr; \
                                      f = fPtr[ 0 ]; \
                                      ptr += sizeof( float ); }

#define EXTRACT_UINT32( ptr, uint ) { UInt32 *dPtr = (UInt32 *)ptr; \
                                      uint = dPtr[ 0 ]; ptr += sizeof( UInt32 ); }


static hsScalar sCurrMinWidth = 0;

///////////////////////////////////////////////////////////////////////////////
//// Particles ////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//// Particle Processing Inlines //////////////////////////////////////////////
//  Thanks to the <cough> beauty of C++, the internal loop of 
//  IFillParticlePolys would be horrendous without these inlines (with them,
//  it's just slightly annoying). The goal is to make the code easier to
//  maintain without loosing speed (hence the inlines, which will *hopefully*
//  remove the function call overhead....)

void inline IInlSetParticlePathFollow( const plParticleCore &particle, const hsMatrix44& viewToWorld, 
                                      hsVector3 &xVec, hsVector3 &yVec, hsVector3 &zVec )
{

    /// Follow path specified by interpreting orientation as a velocity vector.

    hsVector3 viewDir(&particle.fPos, &viewToWorld.GetTranslate());
    hsFastMath::NormalizeAppr(viewDir);

    zVec = viewDir;

    const hsVector3& orientation = (const hsVector3)(particle.fOrientation);
    yVec = orientation - orientation.InnerProduct(viewDir) * viewDir;
    hsFastMath::NormalizeAppr(yVec);

    xVec = yVec % viewDir;
    hsFastMath::NormalizeAppr(xVec);
    xVec *= particle.fHSize;

    yVec *= particle.fVSize;
}

void inline IInlSetParticlePathStretch( const plParticleCore &particle, const hsMatrix44& viewToWorld, 
                                      hsVector3 &xVec, hsVector3 &yVec, hsVector3 &zVec )
{

    /// Follow path specified by interpreting orientation as a velocity vector.

    // Well, that's one way to do it, but it has a bit of a flaw. If the length of the
    // particle doesn't match the distance it's moved each frame, you get a nasty strobing
    // effect, particularly as particles move faster. One of those things they don't teach
    // you in the math class you slept through.
    // So what we want is for the tail of the particle this frame to be where the head of
    // the particle was last frame.
    // First thing changed was the orientation passed in is now the change in position for 
    // this frame (was the last frame's velocity).
    // zVec will still be the normalized vector from the eye to the particle (in world space).
    // Does zVec ever get used? Hmm, only gets used for the normal facing the camera.
    // This thing gets a lot faster and cleaner when we just use the view axes to compute.
    // So zVec is just -viewDir.
    // yVec = orientation - orientation.InnerProduct(zVec) * zVec
    // xVec = yVec % zVec
    // To be really correct, you can normalize xVec, because the particle doesn't get fatter
    // as it goes faster, just longer. Costs a little more, but...
    // And it also raises the question of what to do with the user supplied sizes. Again,
    // for correctness, we want to add the size to the length of the displacement.
    // We could afford that (maybe) by doing a fast normalize on yVec, then multiplying
    // by orientation.InnerProduct(yVec) + fVSize.
    // So the new stuff we need here are:
    //  The particle (to get the size out of).
    //  The viewToWorld transform (we can pull the eyePt and viewDir out of that.
    // Note that we could probably slim away a normalize or two, but the actual number
    // of normalizes we're doing hasn't gone up, I've just moved them up from IInlSetParticlePoints().

    hsVector3 viewDir(&particle.fPos, &viewToWorld.GetTranslate());
    hsScalar invD = hsFastMath::InvSqrtAppr(viewDir.MagnitudeSquared());
    viewDir *= invD;

    zVec = viewDir;

    const hsVector3& orientation = (const hsVector3)(particle.fOrientation);
    // We don't want the projection of orientation orthogonal to viewDir here,
    // it's okay (and looks better) if yVec isn't in the image plane, we just
    // want to make sure that xVec is in the image plane. Might want to make
    // the same change to IInlSetParticlePathFollow(), but I haven't checked
    // that yet. Artifact to look for is particles starting to look goofy
    // as their orientation gets close in direction to viewDir. mf
    yVec = orientation;
    hsFastMath::NormalizeAppr(yVec);

    xVec = yVec % viewDir; // cross product of two orthonormal vectors, no need to normalize.

    hsScalar xLen = particle.fHSize;
    if( xLen * invD < sCurrMinWidth )
        xLen = sCurrMinWidth / invD;
    xVec *= xLen;

    hsScalar len = yVec.InnerProduct(orientation);
    // Might want to give it a little boost to overlap itself (and compensate for the massive
    // transparent border the artists love). But they can do that themselves with the VSize.
//  len *= 1.5f; 
    len += particle.fVSize;
    yVec *= len * -1.f;
}

void inline IInlSetParticlePathFlow( const plParticleCore &particle, const hsMatrix44& viewToWorld, 
                                      hsVector3 &xVec, hsVector3 &yVec, hsVector3 &zVec )
{

    // Okay, all the notes for SetParticlePathStretch apply here too. The only
    // difference is that we're going to keep the area of the particle constant,
    // so the longer it stretches, the narrower it gets orthogonal to the velocity.

    hsVector3 viewDir(&particle.fPos, &viewToWorld.GetTranslate());
    hsScalar invD = hsFastMath::InvSqrtAppr(viewDir.MagnitudeSquared());
    viewDir *= invD;

    zVec = viewDir;

    const hsVector3& orientation = (const hsVector3)(particle.fOrientation);
    // We don't want the projection of orientation orthogonal to viewDir here,
    // it's okay (and looks better) if yVec isn't in the image plane, we just
    // want to make sure that xVec is in the image plane. Might want to make
    // the same change to IInlSetParticlePathFollow(), but I haven't checked
    // that yet. Artifact to look for is particles starting to look goofy
    // as their orientation gets close in direction to viewDir. mf
    yVec = orientation;
    hsFastMath::NormalizeAppr(yVec);

    xVec = yVec % viewDir; // cross product of two orthonormal vectors, no need to normalize.

    hsScalar len = yVec.InnerProduct(orientation);

    hsScalar xLen = particle.fHSize * hsFastMath::InvSqrtAppr(1.f + len * sInvDelSecs);
    if( xLen * invD < sCurrMinWidth )
        xLen = sCurrMinWidth / invD;
    xVec *= xLen;

    // Might want to give it a little boost to overlap itself (and compensate for the massive
    // transparent border the artists love). But they can do that themselves with the VSize.
    len += particle.fVSize;
    yVec *= len * -2.f;

}

void inline IInlSetParticleExplicit( const hsMatrix44 &viewToWorld, const plParticleCore &particle,
                                      hsVector3 &xVec, hsVector3 &yVec, hsVector3 &zVec )
{
    const hsVector3& orientation = (const hsVector3)(particle.fOrientation);
#if 0 // See notes below - mf
    zVec.Set( 0, 0, -1 );
    yVec.Set( &orientation );
    zVec = viewToWorld * zVec;
    yVec = viewToWorld * yVec;
    xVec = yVec % zVec;
#else // See notes below - mf
    // The above has a bit of a problem with wide field of view. All of the
    // particles are facing the same direction with respect to lighting,
    // even though some are to the left and some are to the right of the camera
    // (when facing the center of the system). We'll also start seeing them side on
    // as they get to the edge of the screen. Fortunately, it's actually faster
    // to calculate the vector from camera to particle and normalize it than
    // to transform the vector (0,0,-1) (though not as fast as pulling the
    // camera direction directly from the viewToWorld).
    hsVector3 del(&particle.fPos, &viewToWorld.GetTranslate());
    hsFastMath::NormalizeAppr(del);
    zVec = del;
    yVec.Set(&(viewToWorld * orientation));
    xVec = yVec % zVec;
#endif // See notes below - mf

    xVec = hsFastMath::NormalizeAppr( xVec ) * particle.fHSize;
    yVec = hsFastMath::NormalizeAppr( yVec ) * particle.fVSize;
}

void inline IInlSetParticlePoints( const hsVector3 &xVec, const hsVector3 &yVec, const plParticleCore &particle,
                                   hsPoint3 *partPts, UInt32 &partColor )
{
    /// Do the 4 verts for this particle
    partPts[ 0 ] = partPts[ 1 ] = partPts[ 2 ] = partPts[ 3 ] = particle.fPos;
    partPts[ 0 ] +=  xVec - yVec;
    partPts[ 1 ] += -xVec - yVec;
    partPts[ 2 ] += -xVec + yVec;
    partPts[ 3 ] +=  xVec + yVec;

    partColor = particle.fColor;
}

void inline IInlSetParticlePointsStretch( const hsVector3 &xVec, const hsVector3 &yVec, const plParticleCore &particle,
                                   hsPoint3 *partPts, UInt32 &partColor )
{
    /// Do the 4 verts for this particle
    partPts[ 0 ] = partPts[ 1 ] = partPts[ 2 ] = partPts[ 3 ] = particle.fPos;
    partPts[ 0 ] +=  xVec + yVec;
    partPts[ 1 ] += -xVec + yVec;
    partPts[ 2 ] += -xVec;
    partPts[ 3 ] +=  xVec;

    partColor = particle.fColor;
}

void inline IInlStuffParticle1UV( UInt8 *&destPtr, const hsPoint3 *partPts, const hsVector3 &partNorm,
                                  const UInt32 &partColor, const plParticleCore &particle )
{
    UInt8       j;


    for( j = 0; j < 4; j++ )
    {
        STUFF_POINT( destPtr, partPts[ j ] );
        STUFF_POINT( destPtr, partNorm );
        STUFF_UINT32( destPtr, partColor );
        STUFF_UINT32( destPtr, 0 );
        STUFF_POINT( destPtr, particle.fUVCoords[ j ] );
    }           
}

void inline IInlStuffParticleNoUVs( UInt8 *&destPtr, const hsPoint3 *partPts, const hsVector3 &partNorm,
                                  const UInt32 &partColor )
{
    UInt8       j;


    for( j = 0; j < 4; j++ )
    {
        STUFF_POINT( destPtr, partPts[ j ] );
        STUFF_POINT( destPtr, partNorm );
        STUFF_UINT32( destPtr, partColor );
        STUFF_UINT32( destPtr, 0 );
    }           
}

void inline IInlSetNormalViewFace( hsVector3 &partNorm, const hsVector3 &zVec )
{
    partNorm = -zVec;
}

void inline IInlSetNormalStrongestLight( hsVector3 &partNorm, const plParticleCore &particle, 
                                        const plOmniLightInfo *omniLight, const plDirectionalLightInfo *directionLight, const hsVector3 &zVec )
{
    if( omniLight != nil )
    {
        partNorm.Set( &particle.fPos, &omniLight->GetWorldPosition() );
        partNorm = -partNorm;
    }
    else if( directionLight != nil )
    {
        partNorm = -directionLight->GetWorldDirection();
    }
    else
        partNorm = -zVec;
    partNorm = hsFastMath::NormalizeAppr( partNorm );
}

void inline IInlSetNormalExplicit( hsVector3 &partNorm, const plParticleCore &particle )
{
    partNorm = particle.fNormal;
}

//// IIPL Functions (Internal-Inline-Particle-Loop) ///////////////////////////
//  Function names go as follows:
//      IIPL_u_o_n()
//      where u is 1UV or 0UV (for 1 UV channel or no UV channels),
//      o is OVel (for orientation-follows-velocity) or OExp (for orientation-is-explicit)
//      and n is NViewFace (for normals facing view), NLite (for facing strongest light)
//              or NExp (for explicit)

#define IIPL_PROLOG         \
    UInt32      i, partColor; \
    hsVector3   xVec, yVec, zVec, partNorm; \
    hsPoint3    partPts[ 4 ]; \
    for( i = 0; i < numParticles; i++ )

void inline IIPL_1UV_OVel_NViewFace( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFollow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalViewFace( partNorm, zVec );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_1UV_OVel_NLite( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr,
                                const plOmniLightInfo *omniLight, const plDirectionalLightInfo *directionLight )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFollow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalStrongestLight( partNorm, particles[ i ], omniLight, directionLight, zVec );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_1UV_OVel_NExp( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFollow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalExplicit( partNorm, particles[ i ] );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_1UV_OStr_NViewFace( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathStretch( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalViewFace( partNorm, zVec );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_1UV_OStr_NLite( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr,
                                const plOmniLightInfo *omniLight, const plDirectionalLightInfo *directionLight )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathStretch( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalStrongestLight( partNorm, particles[ i ], omniLight, directionLight, zVec );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_1UV_OStr_NExp( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathStretch( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalExplicit( partNorm, particles[ i ] );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_1UV_OFlo_NViewFace( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFlow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalViewFace( partNorm, zVec );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_1UV_OFlo_NLite( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr,
                                const plOmniLightInfo *omniLight, const plDirectionalLightInfo *directionLight )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFlow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalStrongestLight( partNorm, particles[ i ], omniLight, directionLight, zVec );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_1UV_OFlo_NExp( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFlow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalExplicit( partNorm, particles[ i ] );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_1UV_OExp_NViewFace( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticleExplicit( viewToWorld, particles[ i ], xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalViewFace( partNorm, zVec );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_1UV_OExp_NLite( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr,
                                const plOmniLightInfo *omniLight, const plDirectionalLightInfo *directionLight )
{
    IIPL_PROLOG
    {
        IInlSetParticleExplicit( viewToWorld, particles[ i ], xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalStrongestLight( partNorm, particles[ i ], omniLight, directionLight, zVec );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_1UV_OExp_NExp( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticleExplicit( viewToWorld, particles[ i ], xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalExplicit( partNorm, particles[ i ] );
        IInlStuffParticle1UV( destPtr, partPts, partNorm, partColor, particles[ i ] );
    }
}

void inline IIPL_0UV_OVel_NViewFace( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFollow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalViewFace( partNorm, zVec );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

void inline IIPL_0UV_OVel_NLite( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr,
                                const plOmniLightInfo *omniLight, const plDirectionalLightInfo *directionLight )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFollow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalStrongestLight( partNorm, particles[ i ], omniLight, directionLight, zVec );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

void inline IIPL_0UV_OVel_NExp( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFollow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalExplicit( partNorm, particles[ i ] );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

void inline IIPL_0UV_OStr_NViewFace( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathStretch( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalViewFace( partNorm, zVec );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

void inline IIPL_0UV_OStr_NLite( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr,
                                const plOmniLightInfo *omniLight, const plDirectionalLightInfo *directionLight )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathStretch( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalStrongestLight( partNorm, particles[ i ], omniLight, directionLight, zVec );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

void inline IIPL_0UV_OStr_NExp( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathStretch( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalExplicit( partNorm, particles[ i ] );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

void inline IIPL_0UV_OFlo_NViewFace( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFlow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalViewFace( partNorm, zVec );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

void inline IIPL_0UV_OFlo_NLite( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr,
                                const plOmniLightInfo *omniLight, const plDirectionalLightInfo *directionLight )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFlow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalStrongestLight( partNorm, particles[ i ], omniLight, directionLight, zVec );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

void inline IIPL_0UV_OFlo_NExp( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticlePathFlow( particles[ i ], viewToWorld, xVec, yVec, zVec );
        IInlSetParticlePointsStretch( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalExplicit( partNorm, particles[ i ] );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

void inline IIPL_0UV_OExp_NViewFace( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticleExplicit( viewToWorld, particles[ i ], xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalViewFace( partNorm, zVec );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

void inline IIPL_0UV_OExp_NLite( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr,
                                const plOmniLightInfo *omniLight, const plDirectionalLightInfo *directionLight )
{
    IIPL_PROLOG
    {
        IInlSetParticleExplicit( viewToWorld, particles[ i ], xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalStrongestLight( partNorm, particles[ i ], omniLight, directionLight, zVec );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

void inline IIPL_0UV_OExp_NExp( const UInt32 &numParticles, const plParticleCore *particles, const hsMatrix44& viewToWorld, UInt8 *&destPtr )
{
    IIPL_PROLOG
    {
        IInlSetParticleExplicit( viewToWorld, particles[ i ], xVec, yVec, zVec );
        IInlSetParticlePoints( xVec, yVec, particles[ i ], partPts, partColor );
        IInlSetNormalExplicit( partNorm, particles[ i ] );
        IInlStuffParticleNoUVs( destPtr, partPts, partNorm, partColor );
    }
}

//// IFillParticlePolys ///////////////////////////////////////////////////////
//  Takes a list of particles and makes the polys for them.
#include "hsTimer.h"
#include "plProfile.h"
plProfile_CreateTimer("Fill Polys", "Particles", ParticleFillPoly);

void plParticleFiller::FillParticles(plPipeline* pipe, plDrawableSpans* drawable, plParticleSpan* span)
{
    if (!span->fSource || span->fNumParticles <= 0)
        return;

    plProfile_BeginTiming(ParticleFillPoly);

    sInvDelSecs = hsTimer::GetDelSysSeconds();
    if( sInvDelSecs > 0 )
        sInvDelSecs = 1.f / sInvDelSecs;

    const plParticleCore* particles = span->fSource->GetParticleArray();
    const UInt32 numParticles = span->fNumParticles;

    plGBufferGroup* group = drawable->GetBufferGroup(span->fGroupIdx);

    UInt8* destPtr = group->GetVertBufferData(span->fVBufferIdx);

    destPtr += span->fVStartIdx * group->GetVertexSize();

    /// Get the z vector (pointing away from the camera) in worldspace
    hsMatrix44 viewToWorld = pipe->GetCameraToWorld();

    plOmniLightInfo* omniLight = nil;
    plDirectionalLightInfo* directionLight = nil;

    /// Get strongest light, if there is one, for normal generation
    if( span->GetNumLights( false ) > 0 )
    {
        omniLight = plOmniLightInfo::ConvertNoRef( span->GetLight( 0, false ) );
        directionLight = plDirectionalLightInfo::ConvertNoRef( span->GetLight( 0, false ) );
    }

    /// Fill with 1 UV channel
    if( group->GetNumUVs() == 1 )
    {
        /// Switch on orientation
        if( span->fSource->fMiscFlags & plParticleEmitter::kOrientationVelocityBased )
        {
            /// Switch on normal generation
            if( span->fSource->fMiscFlags & plParticleEmitter::kNormalViewFacing )
                IIPL_1UV_OVel_NViewFace( numParticles, particles, viewToWorld, destPtr );

            else if( span->fSource->fMiscFlags & plParticleEmitter::kNormalNearestLight )
                IIPL_1UV_OVel_NLite( numParticles, particles, viewToWorld, destPtr, omniLight, directionLight );

            else
                IIPL_1UV_OVel_NExp( numParticles, particles, viewToWorld, destPtr );
        }
        else if( span->fSource->fMiscFlags & plParticleEmitter::kOrientationVelocityStretch )
        {
            sCurrMinWidth = pipe->GetViewTransform().GetOrthoWidth() / pipe->GetViewTransform().GetScreenWidth() * 0.75f;

            /// Switch on normal generation
            if( span->fSource->fMiscFlags & plParticleEmitter::kNormalViewFacing )
                IIPL_1UV_OStr_NViewFace( numParticles, particles, viewToWorld, destPtr );

            else if( span->fSource->fMiscFlags & plParticleEmitter::kNormalNearestLight )
                IIPL_1UV_OStr_NLite( numParticles, particles, viewToWorld, destPtr, omniLight, directionLight );

            else
                IIPL_1UV_OStr_NExp( numParticles, particles, viewToWorld, destPtr );
        }
        else if( span->fSource->fMiscFlags & plParticleEmitter::kOrientationVelocityFlow )
        {
            sCurrMinWidth = pipe->GetViewTransform().GetOrthoWidth() / pipe->GetViewTransform().GetScreenWidth() * 0.75f;

            /// Switch on normal generation
            if( span->fSource->fMiscFlags & plParticleEmitter::kNormalViewFacing )
                IIPL_1UV_OFlo_NViewFace( numParticles, particles, viewToWorld, destPtr );

            else if( span->fSource->fMiscFlags & plParticleEmitter::kNormalNearestLight )
                IIPL_1UV_OFlo_NLite( numParticles, particles, viewToWorld, destPtr, omniLight, directionLight );

            else
                IIPL_1UV_OFlo_NExp( numParticles, particles, viewToWorld, destPtr );
        }
        else    // Orientation explicit
        {
            /// Switch on normal generation
            if( span->fSource->fMiscFlags & plParticleEmitter::kNormalViewFacing )
                IIPL_1UV_OExp_NViewFace( numParticles, particles, viewToWorld, destPtr );

            else if( span->fSource->fMiscFlags & plParticleEmitter::kNormalNearestLight )
                IIPL_1UV_OExp_NLite( numParticles, particles, viewToWorld, destPtr, omniLight, directionLight );

            else
                IIPL_1UV_OExp_NExp( numParticles, particles, viewToWorld, destPtr );
        }
    }
    else
    /// Fill with no UV channels
    {
        /// Switch on orientation
        if( span->fSource->fMiscFlags & plParticleEmitter::kOrientationVelocityBased )
        {
            /// Switch on normal generation
            if( span->fSource->fMiscFlags & plParticleEmitter::kNormalViewFacing )
                IIPL_0UV_OVel_NViewFace( numParticles, particles, viewToWorld, destPtr );

            else if( span->fSource->fMiscFlags & plParticleEmitter::kNormalNearestLight )
                IIPL_0UV_OVel_NLite( numParticles, particles, viewToWorld, destPtr, omniLight, directionLight );

            else
                IIPL_0UV_OVel_NExp( numParticles, particles, viewToWorld, destPtr );
        }
        else if( span->fSource->fMiscFlags & plParticleEmitter::kOrientationVelocityStretch )
        {
            sCurrMinWidth = pipe->GetViewTransform().GetOrthoWidth() / pipe->GetViewTransform().GetScreenWidth() * 0.75f;

            /// Switch on normal generation
            if( span->fSource->fMiscFlags & plParticleEmitter::kNormalViewFacing )
                IIPL_0UV_OStr_NViewFace( numParticles, particles, viewToWorld, destPtr );

            else if( span->fSource->fMiscFlags & plParticleEmitter::kNormalNearestLight )
                IIPL_0UV_OStr_NLite( numParticles, particles, viewToWorld, destPtr, omniLight, directionLight );

            else
                IIPL_0UV_OStr_NExp( numParticles, particles, viewToWorld, destPtr );
        }
        else if( span->fSource->fMiscFlags & plParticleEmitter::kOrientationVelocityFlow )
        {
            sCurrMinWidth = pipe->GetViewTransform().GetOrthoWidth() / pipe->GetViewTransform().GetScreenWidth() * 0.75f;

            /// Switch on normal generation
            if( span->fSource->fMiscFlags & plParticleEmitter::kNormalViewFacing )
                IIPL_0UV_OFlo_NViewFace( numParticles, particles, viewToWorld, destPtr );

            else if( span->fSource->fMiscFlags & plParticleEmitter::kNormalNearestLight )
                IIPL_0UV_OFlo_NLite( numParticles, particles, viewToWorld, destPtr, omniLight, directionLight );

            else
                IIPL_0UV_OFlo_NExp( numParticles, particles, viewToWorld, destPtr );
        }
        else    // Orientation explicit
        {
            /// Switch on normal generation
            if( span->fSource->fMiscFlags & plParticleEmitter::kNormalViewFacing )
                IIPL_0UV_OExp_NViewFace( numParticles, particles, viewToWorld, destPtr );

            else if( span->fSource->fMiscFlags & plParticleEmitter::kNormalNearestLight )
                IIPL_0UV_OExp_NLite( numParticles, particles, viewToWorld, destPtr, omniLight, directionLight );

            else
                IIPL_0UV_OExp_NExp( numParticles, particles, viewToWorld, destPtr );
        }
    }

    /// All done!
    plProfile_EndTiming(ParticleFillPoly);
}

void plParticleFiller::FillParticlePolys(plPipeline* pipe, plDrawInterface* di)
{
    if( !di )
        return; // should probably be an asserted error.

    plDrawableSpans* drawable = plDrawableSpans::ConvertNoRef(di->GetDrawable(0));
    if( !drawable )
        return;

    // Currently, the di always points to exactly 1 drawable with 1 span index. If
    // that changes, this would just become a loop.
    UInt32 diIndex = di->GetDrawableMeshIndex(0);
    hsAssert(diIndex >= 0, "Bogus input to fill particles");

    const plDISpanIndex& diSpans = drawable->GetDISpans(diIndex);
    int i;
    for( i = 0; i < diSpans.GetCount(); i++ )
    {
        UInt32 spanIdx = diSpans[i];
        hsAssert(drawable->GetSpan(spanIdx), "Bogus input to fill particles");
        hsAssert(drawable->GetSpan(spanIdx)->fTypeMask & plSpan::kParticleSpan, "Bogus input to fill particles");

        // Safe cast, since we just checked the type mask.
        plParticleSpan* span = (plParticleSpan*)drawable->GetSpan(spanIdx);

        if( !span->fSource )
            return; // Nothing to do, it's idle.

        FillParticles(pipe, drawable, span);
    }

}