// Ridah, cg_trails.c - draws a trail using multiple junction points

#include "cg_local.h"

typedef struct trailJunc_s
{
  struct trailJunc_s  *nextGlobal, *prevGlobal;  // next junction in the global list it is in (free or used)
  struct trailJunc_s  *nextJunc;         // next junction in the trail
  struct trailJunc_s  *nextHead, *prevHead;    // next head junc in the world

  qboolean            inuse, freed;
  int                 ownerIndex;
  qhandle_t           shader;

  int                 sType;
  int                 flags;
  float               sTex;
  vec3_t              pos;
  int                 spawnTime, endTime;
  float               alphaStart, alphaEnd;
  vec3_t              colorStart, colorEnd;
  float               widthStart, widthEnd;

  // current settings
  float               alpha;
  float               width;
  vec3_t              color;

} trailJunc_t;

#define MAX_TRAILJUNCS  4096

trailJunc_t trailJuncs[ MAX_TRAILJUNCS ];
trailJunc_t *freeTrails, *activeTrails;
trailJunc_t *headTrails;

qboolean initTrails = qfalse;

int numTrailsInuse;

/*
===============
CG_ClearTrails
===============
*/
void CG_ClearTrails( void )
{
  int   i;

  memset( trailJuncs, 0, sizeof( trailJunc_t ) * MAX_TRAILJUNCS );

  freeTrails = trailJuncs;
  activeTrails = NULL;
  headTrails = NULL;

  for( i = 0; i < MAX_TRAILJUNCS; i++ )
  {
    trailJuncs[ i ].nextGlobal = &trailJuncs[ i + 1 ];

    if( i > 0 )
      trailJuncs[ i ].prevGlobal = &trailJuncs[ i - 1 ];
    else
      trailJuncs[ i ].prevGlobal = NULL;

    trailJuncs[ i ].inuse = qfalse;
  }
  
  trailJuncs[ MAX_TRAILJUNCS - 1 ].nextGlobal = NULL;

  initTrails = qtrue;
  numTrailsInuse = 0;
}

/*
===============
CG_SpawnTrailJunc
===============
*/
trailJunc_t *CG_SpawnTrailJunc( trailJunc_t *headJunc )
{
  trailJunc_t *j;

  if( !freeTrails )
    return NULL;

  if( cg_paused.integer )
    return NULL;

  // select the first free trail, and remove it from the list
  j = freeTrails;
  freeTrails = j->nextGlobal;
  
  if( freeTrails )
    freeTrails->prevGlobal = NULL;

  j->nextGlobal = activeTrails;
  
  if( activeTrails )
    activeTrails->prevGlobal = j;
  
  activeTrails = j;
  j->prevGlobal = NULL;
  j->inuse = qtrue;
  j->freed = qfalse;

  // if this owner has a headJunc, add us to the start
  if( headJunc )
  {
    // remove the headJunc from the list of heads
    if( headJunc == headTrails )
    {
      headTrails = headJunc->nextHead;
      
      if( headTrails )
        headTrails->prevHead = NULL;
    }
    else
    {
      if( headJunc->nextHead )
        headJunc->nextHead->prevHead = headJunc->prevHead;
      
      if( headJunc->prevHead )
        headJunc->prevHead->nextHead = headJunc->nextHead;
    }
    headJunc->prevHead = NULL;
    headJunc->nextHead = NULL;
  }
  
  // make us the headTrail
  if( headTrails )
    headTrails->prevHead = j;
  
  j->nextHead = headTrails;
  j->prevHead = NULL;
  headTrails = j;

  j->nextJunc = headJunc; // if headJunc is NULL, then we'll just be the end of the list

  numTrailsInuse++;

  // debugging
//  CG_Printf( "NumTrails: %i\n", numTrailsInuse );

  return j;
}


/*
===============
CG_AddTrailJunc

  returns the index of the trail junction created

  Used for generic trails
===============
*/
int CG_AddTrailJunc( int headJuncIndex, qhandle_t shader, int spawnTime, int sType, vec3_t pos,
                     int trailLife, float alphaStart, float alphaEnd, float startWidth,
                     float endWidth, int flags, vec3_t colorStart, vec3_t colorEnd,
                     float sRatio, float animSpeed )
{
  trailJunc_t *j, *headJunc;

  if( headJuncIndex > 0 )
  {
    headJunc = &trailJuncs[ headJuncIndex - 1 ];

    if( !headJunc->inuse )
      headJunc = NULL;
  }
  else
    headJunc = NULL;

  j = CG_SpawnTrailJunc( headJunc );
  
  if( !j )
  {
//    CG_Printf("couldnt spawn trail junc\n");
    return 0;
  }

  if( alphaStart > 1.0 )
    alphaStart = 1.0;
  
  if( alphaStart < 0.0 )
    alphaStart = 0.0;
  
  if( alphaEnd > 1.0 )
    alphaEnd = 1.0;
  
  if( alphaEnd < 0.0 )
    alphaEnd = 0.0;

  // setup the trail junction
  j->shader = shader;
  j->sType = sType;
  VectorCopy( pos, j->pos );
  j->flags = flags;

  j->spawnTime = spawnTime;
  j->endTime = spawnTime + trailLife;

  VectorCopy( colorStart, j->colorStart );
  VectorCopy( colorEnd, j->colorEnd );

  j->alphaStart = alphaStart;
  j->alphaEnd = alphaEnd;

  j->widthStart = startWidth;
  j->widthEnd = endWidth;

  if( sType == STYPE_REPEAT )
  {
    if( headJunc )
      j->sTex = headJunc->sTex + ( ( Distance( headJunc->pos, pos ) / sRatio) / j->widthEnd );
    else
    {
      // FIXME: need a way to specify offset timing
      j->sTex = ( animSpeed * ( 1.0 - ( (float)( cg.time % 1000 ) / 1000.0 ) ) ) / ( sRatio );
//      j->sTex = 0;
    }
  }

  return ( (int)( j - trailJuncs ) + 1 );
}

/*
===============
CG_AddSparkJunc

  returns the index of the trail junction created
===============
*/
int CG_AddSparkJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife,
                     float alphaStart, float alphaEnd, float startWidth, float endWidth )
{
  trailJunc_t *j, *headJunc;

  if( headJuncIndex > 0 )
  {
    headJunc = &trailJuncs[ headJuncIndex - 1 ];

    if( !headJunc->inuse )
      headJunc = NULL;
  }
  else
    headJunc = NULL;

  j = CG_SpawnTrailJunc( headJunc );
  
  if( !j )
    return 0;

  // setup the trail junction
  j->shader = shader;
  j->sType = STYPE_STRETCH;
  VectorCopy( pos, j->pos );
  j->flags = TJFL_NOCULL;   // don't worry about fading up close

  j->spawnTime = cg.time;
  j->endTime = cg.time + trailLife;

  VectorSet( j->colorStart, 1.0, 0.8 + 0.2 * alphaStart, 0.4 + 0.4 * alphaStart );
  VectorSet( j->colorEnd, 1.0, 0.8 + 0.2 * alphaEnd, 0.4 + 0.4 * alphaEnd );
//  VectorScale( j->colorStart, alphaStart, j->colorStart );
//  VectorScale( j->colorEnd, alphaEnd, j->colorEnd );

  j->alphaStart = alphaStart*2;
  j->alphaEnd = alphaEnd*2;
//  j->alphaStart = 1.0;
//  j->alphaEnd = 1.0;

  j->widthStart = startWidth;
  j->widthEnd = endWidth;

  return ( (int)( j - trailJuncs ) + 1 );
}

/*
===============
CG_AddSmokeJunc

  returns the index of the trail junction created
===============
*/
int CG_AddSmokeJunc( int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife,
                     float alpha, float startWidth, float endWidth )
{
#define ST_RATIO  4.0   // sprite image: width / height
  trailJunc_t *j, *headJunc;

  if( headJuncIndex > 0 )
  {
    headJunc = &trailJuncs[ headJuncIndex - 1 ];

    if( !headJunc->inuse )
      headJunc = NULL;
  }
  else
    headJunc = NULL;

  j = CG_SpawnTrailJunc( headJunc );
  
  if( !j )
    return 0;

  // setup the trail junction
  j->shader = shader;
  j->sType = STYPE_REPEAT;
  VectorCopy( pos, j->pos );
  j->flags = TJFL_FADEIN;

  j->spawnTime = cg.time;
  j->endTime = cg.time + trailLife;

  // VectorSet(j->colorStart, 0.2, 0.2, 0.2);
  VectorSet(j->colorStart, 0.0, 0.0, 0.0);
  // VectorSet(j->colorEnd, 0.1, 0.1, 0.1);
  VectorSet(j->colorEnd, 0.0, 0.0, 0.0);

  j->alphaStart = alpha;
  j->alphaEnd = 0.0;

  j->widthStart = startWidth;
  j->widthEnd = endWidth;

  if( headJunc )
    j->sTex = headJunc->sTex + ( ( Distance( headJunc->pos, pos ) / ST_RATIO ) / j->widthEnd );
  else
  {
    // first junction, so this will become the "tail" very soon, make it fade out
    j->sTex = 0;
    j->alphaStart = 0.0;
    j->alphaEnd = 0.0;
  }

  return ( (int)( j - trailJuncs ) + 1 );
}

void CG_KillTrail( trailJunc_t *t );

/*
===========
CG_FreeTrailJunc
===========
*/
void CG_FreeTrailJunc( trailJunc_t *junc )
{
  // kill any juncs after us, so they aren't left hanging
  if( junc->nextJunc )
    CG_KillTrail( junc );

  // make it non-active
  junc->inuse = qfalse;
  junc->freed = qtrue;
  
  if( junc->nextGlobal )
    junc->nextGlobal->prevGlobal = junc->prevGlobal;
  
  if( junc->prevGlobal )
    junc->prevGlobal->nextGlobal = junc->nextGlobal;
  
  if( junc == activeTrails )
    activeTrails = junc->nextGlobal;

  // if it's a head, remove it
  if( junc == headTrails )
    headTrails = junc->nextHead;
  
  if( junc->nextHead )
    junc->nextHead->prevHead = junc->prevHead;
  
  if( junc->prevHead )
    junc->prevHead->nextHead = junc->nextHead;
  
  junc->nextHead = NULL;
  junc->prevHead = NULL;

  // stick it in the free list
  junc->prevGlobal = NULL;
  junc->nextGlobal = freeTrails;
  
  if( freeTrails )
    freeTrails->prevGlobal = junc;

  freeTrails = junc;

  numTrailsInuse--;
}

/*
===========
CG_KillTrail
===========
*/
void CG_KillTrail( trailJunc_t *t )
{
  trailJunc_t *next;

  next = t->nextJunc;

  // kill the trail here
  t->nextJunc = NULL;

  if( next )
    CG_FreeTrailJunc( next );
}

/*
==============
CG_AddTrailToScene

  TODO: this can do with some major optimization
==============
*/
static vec3_t vforward, vright, vup;

//TA: staticised to please QVM
  #define MAX_TRAIL_VERTS   2048
static polyVert_t verts[ MAX_TRAIL_VERTS ];
static polyVert_t outVerts[ MAX_TRAIL_VERTS * 3 ];

void CG_AddTrailToScene( trailJunc_t *trail, int iteration, int numJuncs )
{
  int         k, i, n, l, numOutVerts;
  polyVert_t  mid;
  float       mod[ 4 ];
  float       sInc = 0.0f, s = 0.0f; // TTimo: init
  trailJunc_t *j, *jNext;
  vec3_t      fwd, up, p, v;
  
  // clipping vars
  #define TRAIL_FADE_CLOSE_DIST 64.0
  #define TRAIL_FADE_FAR_SCALE  4.0
  vec3_t  viewProj;
  float viewDist, fadeAlpha;

  // add spark shader at head position
  if( trail->flags & TJFL_SPARKHEADFLARE )
  {
    j = trail;
    VectorCopy( j->pos, p );
    VectorMA( p, -j->width * 2, vup, p );  
    VectorMA( p, -j->width * 2, vright, p ); 
    VectorCopy( p, verts[ 0 ].xyz );
    verts[ 0 ].st[ 0 ] = 0; 
    verts[ 0 ].st[ 1 ] = 0; 
    verts[ 0 ].modulate[ 0 ] = 255; 
    verts[ 0 ].modulate[ 1 ] = 255; 
    verts[ 0 ].modulate[ 2 ] = 255; 
    verts[ 0 ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 );

    VectorCopy( j->pos, p );
    VectorMA( p, -j->width * 2, vup, p );  
    VectorMA( p, j->width * 2, vright, p );  
    VectorCopy( p, verts[ 1 ].xyz ); 
    verts[ 1 ].st[ 0 ] = 0; 
    verts[ 1 ].st[ 1 ] = 1; 
    verts[ 1 ].modulate[ 0 ] = 255; 
    verts[ 1 ].modulate[ 1 ] = 255; 
    verts[ 1 ].modulate[ 2 ] = 255; 
    verts[ 1 ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 ); 

    VectorCopy( j->pos, p );
    VectorMA( p, j->width * 2, vup, p ); 
    VectorMA( p, j->width * 2, vright, p );  
    VectorCopy( p, verts[ 2 ].xyz ); 
    verts[ 2 ].st[ 0 ] = 1; 
    verts[ 2 ].st[ 1 ] = 1; 
    verts[ 2 ].modulate[ 0 ] = 255; 
    verts[ 2 ].modulate[ 1 ] = 255; 
    verts[ 2 ].modulate[ 2 ] = 255; 
    verts[ 2 ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 ); 

    VectorCopy( j->pos, p );
    VectorMA( p,  j->width * 2, vup, p );  
    VectorMA( p, -j->width * 2, vright, p ); 
    VectorCopy( p, verts[ 3 ].xyz ); 
    verts[ 3 ].st[ 0 ] = 1; 
    verts[ 3 ].st[ 1 ] = 0; 
    verts[ 3 ].modulate[ 0 ] = 255; 
    verts[ 3 ].modulate[ 1 ] = 255; 
    verts[ 3 ].modulate[ 2 ] = 255; 
    verts[ 3 ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 ); 

    trap_R_AddPolyToScene( cgs.media.sparkFlareShader, 4, verts );
  }

//  if (trail->flags & TJFL_CROSSOVER && iteration < 1) {
//    iteration = 1;
//  }

  if( !numJuncs )
  {
    // first count the number of juncs in the trail
    j = trail;
    numJuncs = 0;
    sInc = 0;
    
    while( j )
    {
      numJuncs++;

      // check for a dead next junc
      if( !j->inuse && j->nextJunc && !j->nextJunc->inuse )
        CG_KillTrail( j );
      else if( j->nextJunc && j->nextJunc->freed )
      {
        // not sure how this can happen, but it does, and causes infinite loops
        j->nextJunc = NULL;
      }

      if( j->nextJunc )
        sInc += VectorDistance( j->nextJunc->pos, j->pos );

      j = j->nextJunc;
    }
  }

  if( numJuncs < 2 )
    return;

  if( trail->sType == STYPE_STRETCH )
  {
    //sInc = ((1.0 - 0.1) / (float)(numJuncs)); // hack, the end of funnel shows a bit of the start (looping)
    s = 0.05;
    //s = 0.05;
  }
  else if( trail->sType == STYPE_REPEAT )
    s = trail->sTex;

  // now traverse the list
  j = trail;
  jNext = j->nextJunc;
  i = 0;
  while( jNext )
  {
    // first get the directional vectors to the next junc
    VectorSubtract( jNext->pos, j->pos, fwd );
    GetPerpendicularViewVector( cg.refdef.vieworg, j->pos, jNext->pos, up );

    // if it's a crossover, draw it twice
    if( j->flags & TJFL_CROSSOVER )
    {
      if( iteration > 0 )
      {
        ProjectPointOntoVector( cg.refdef.vieworg, j->pos, jNext->pos, viewProj );
        VectorSubtract( cg.refdef.vieworg, viewProj, v );
        VectorNormalize( v );

        if( iteration == 1 )
          VectorMA( up, 0.3, v, up );
        else
          VectorMA( up, -0.3, v, up );
        
        VectorNormalize( up );
      }
    }
    // do fading when moving towards the projection point onto the trail segment vector
    else if( !( j->flags & TJFL_NOCULL ) && ( j->widthEnd > 4 || jNext->widthEnd > 4 ) )
    {
      ProjectPointOntoVector( cg.refdef.vieworg, j->pos, jNext->pos, viewProj );
      viewDist = Distance( viewProj, cg.refdef.vieworg );
      
      if( viewDist < ( TRAIL_FADE_CLOSE_DIST * TRAIL_FADE_FAR_SCALE ) )
      {
        if( viewDist < TRAIL_FADE_CLOSE_DIST )
          fadeAlpha = 0.0;
        else
          fadeAlpha = ( viewDist - TRAIL_FADE_CLOSE_DIST ) / ( TRAIL_FADE_CLOSE_DIST * TRAIL_FADE_FAR_SCALE );

        if( fadeAlpha < j->alpha )
          j->alpha = fadeAlpha;

        if( fadeAlpha < jNext->alpha )
          jNext->alpha = fadeAlpha;
      }
    }

    // now output the QUAD for this segment

    // 1 ----
    VectorMA( j->pos, 0.5 * j->width, up, p );
    VectorCopy( p, verts[ i ].xyz );
    verts[ i ].st[ 0 ] = s;
    verts[ i ].st[ 1 ] = 1.0;
    
    for( k = 0; k < 3; k++ )
      verts[ i ].modulate[ k ] = (unsigned char)( j->color[ k ] * 255.0 );
    
    verts[ i ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 );

    // blend this with the previous junc
    if( j != trail )
    {
      VectorAdd( verts[ i ].xyz, verts[ i - 1 ].xyz, verts[ i ].xyz );
      VectorScale( verts[ i ].xyz, 0.5, verts[ i ].xyz );
      VectorCopy( verts[ i ].xyz, verts[ i - 1 ].xyz );
    }
    else if( j->flags & TJFL_FADEIN )
      verts[ i ].modulate[ 3 ] = 0; // fade in

    i++;

    // 2 ----
    VectorMA( p, -1 * j->width, up, p );
    VectorCopy( p, verts[ i ].xyz );
    verts[ i ].st[ 0 ] = s;
    verts[ i ].st[ 1 ] = 0.0;
    
    for( k = 0; k < 3; k++ )
      verts[ i ].modulate[ k ] = (unsigned char)( j->color[ k ] * 255.0 );
    
    verts[ i ].modulate[ 3 ] = (unsigned char)( j->alpha * 255.0 );

    // blend this with the previous junc
    if( j != trail )
    {
      VectorAdd( verts[ i ].xyz, verts[ i - 3 ].xyz, verts[ i ].xyz );
      VectorScale( verts[ i ].xyz, 0.5, verts[ i ].xyz );
      VectorCopy( verts[ i ].xyz, verts[ i - 3 ].xyz );
    }
    else if( j->flags & TJFL_FADEIN )
      verts[ i ].modulate[ 3 ] = 0; // fade in

    i++;

    if( trail->sType == STYPE_REPEAT )
      s = jNext->sTex;
    else
    {
      //s += sInc;
      s += VectorDistance( j->pos, jNext->pos ) / sInc;
      if( s > 1.0 )
        s = 1.0;
    }

    // 3 ----
    VectorMA( jNext->pos, -0.5 * jNext->width, up, p );
    VectorCopy( p, verts[ i ].xyz );
    verts[ i ].st[ 0 ] = s;
    verts[ i ].st[ 1 ] = 0.0;
    
    for( k = 0; k < 3; k++ )
      verts[ i ].modulate[ k ] = (unsigned char)( jNext->color[ k ] * 255.0 );
    
    verts[ i ].modulate[ 3 ] = (unsigned char)( jNext->alpha * 255.0 );
    i++;

    // 4 ----
    VectorMA( p, jNext->width, up, p );
    VectorCopy( p, verts[ i ].xyz );
    verts[ i ].st[ 0 ] = s;
    verts[ i ].st[ 1 ] = 1.0;
    
    for( k = 0; k < 3; k++ )
      verts[ i ].modulate[ k ] = (unsigned char)( jNext->color[ k ] * 255.0 );
    
    verts[ i ].modulate[ 3 ] = (unsigned char)( jNext->alpha * 255.0 );
    i++;

    if( i + 4 > MAX_TRAIL_VERTS )
      break;

    j = jNext;
    jNext = j->nextJunc;
  }

  if( trail->flags & TJFL_FIXDISTORT )
  {
    // build the list of outVerts, by dividing up the QUAD's into 4 Tri's each, so as to allow
    //  any shaped (convex) Quad without bilinear distortion
    for( k = 0, numOutVerts = 0; k < i; k += 4 )
    {
      VectorCopy( verts[ k ].xyz, mid.xyz );
      mid.st[ 0 ] = verts[ k ].st[ 0 ];
      mid.st[ 1 ] = verts[ k ].st[ 1 ];
      
      for( l = 0; l < 4; l++ )
        mod[ l ] = (float)verts[ k ].modulate[ l ];
      
      for( n = 1; n < 4; n++ )
      {
        VectorAdd( verts[ k + n ].xyz, mid.xyz, mid.xyz );
        mid.st[ 0 ] += verts[ k + n ].st[ 0 ];
        mid.st[ 1 ] += verts[ k + n ].st[ 1 ];
        
        for( l = 0; l < 4; l++ )
          mod[ l ] += (float)verts[ k + n ].modulate[ l ];
      }
      
      VectorScale( mid.xyz, 0.25, mid.xyz );
      mid.st[ 0 ] *= 0.25;
      mid.st[ 1 ] *= 0.25;
      
      for( l = 0; l < 4; l++ )
        mid.modulate[ l ] = (unsigned char)( mod[ l ] / 4.0 );

      // now output the tri's
      for( n = 0; n < 4; n++ )
      {
        outVerts[ numOutVerts++ ] = verts[ k + n ];
        outVerts[ numOutVerts++ ] = mid;
        
        if( n < 3 )
          outVerts[ numOutVerts++ ] = verts[ k + n + 1 ];
        else
          outVerts[ numOutVerts++ ] = verts[ k ];
      }
      
    }

    if( !( trail->flags & TJFL_NOPOLYMERGE ) )
      trap_R_AddPolysToScene( trail->shader, 3, &outVerts[ 0 ], numOutVerts / 3 );
    else
    {
      int k;
      
      for( k = 0; k < numOutVerts / 3; k++ )
        trap_R_AddPolyToScene( trail->shader, 3, &outVerts[ k * 3 ] );
    }
  }
  else
  {
    // send the polygons
    // FIXME: is it possible to send a GL_STRIP here? We are actually sending 2x the verts we really need to
    if( !( trail->flags & TJFL_NOPOLYMERGE ) )
      trap_R_AddPolysToScene( trail->shader, 4, &verts[ 0 ], i / 4 );
    else
    {
      int k;
      
      for( k = 0; k < i / 4; k++ )
        trap_R_AddPolyToScene( trail->shader, 4, &verts[ k * 4 ] );
    }
  }

  // do we need to make another pass?
  if( trail->flags & TJFL_CROSSOVER )
  {
    if( iteration < 2 )
      CG_AddTrailToScene( trail, iteration + 1, numJuncs );
  }

}

/*
===============
CG_AddTrails
===============
*/
void CG_AddTrails( void )
{
  float       lifeFrac;
  trailJunc_t *j, *jNext;

  if( !initTrails )
    CG_ClearTrails( );

  //AngleVectors( cg.snap->ps.viewangles, vforward, vright, vup );
  VectorCopy( cg.refdef.viewaxis[ 0 ], vforward );
  VectorCopy( cg.refdef.viewaxis[ 1 ], vright );
  VectorCopy( cg.refdef.viewaxis[ 2 ], vup );

  // update the settings for each junc
  j = activeTrails;
  
  while( j )
  {
    lifeFrac = (float)( cg.time - j->spawnTime ) / (float)( j->endTime - j->spawnTime );
    
    if( lifeFrac >= 1.0 )
    {
      j->inuse = qfalse;      // flag it as dead
      j->width = j->widthEnd;
      j->alpha = j->alphaEnd;
      
      if( j->alpha > 1.0 )
        j->alpha = 1.0;
      else if( j->alpha < 0.0 )
        j->alpha = 0.0;
      
      VectorCopy( j->colorEnd, j->color );
    }
    else
    {
      j->width = j->widthStart + ( j->widthEnd - j->widthStart ) * lifeFrac;
      j->alpha = j->alphaStart + ( j->alphaEnd - j->alphaStart ) * lifeFrac;
      
      if( j->alpha > 1.0 )
        j->alpha = 1.0;
      else if( j->alpha < 0.0 )
        j->alpha = 0.0;
      
      VectorSubtract( j->colorEnd, j->colorStart, j->color );
      VectorMA( j->colorStart, lifeFrac, j->color, j->color );
    }

    j = j->nextGlobal;
  }

  // draw the trailHeads
  j = headTrails;
  
  while( j )
  {
    jNext = j->nextHead;    // in case it gets removed
    
    if( !j->inuse )
      CG_FreeTrailJunc( j );
    else
      CG_AddTrailToScene( j, 0, 0 );

    j = jNext;
  }
}