/*
Copyright (C) 1996-1997 Id Software, 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 2
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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// r_sprite.c

#include "quakedef.h"
#include "r_local.h"

static int        clip_current;
static vec5_t     clip_verts[2][MAXWORKINGVERTS];
static int        sprite_width, sprite_height;

spritedesc_t      r_spritedesc;
  

/*
================
R_RotateSprite
================
*/
void R_RotateSprite (float beamlength)
{
  vec3_t  vec;
  
  if (beamlength == 0.0)
    return;

  VectorScale (r_spritedesc.vpn, -beamlength, vec);
  VectorAdd (r_entorigin, vec, r_entorigin);
  VectorSubtract (modelorg, vec, modelorg);
}


/*
=============
R_ClipSpriteFace

Clips the winding at clip_verts[clip_current] and changes clip_current
Throws out the back side
==============
*/
int R_ClipSpriteFace (int nump, clipplane_t *pclipplane)
{
  int   i, outcount;
  float dists[MAXWORKINGVERTS+1];
  float frac, clipdist, *pclipnormal;
  float *in, *instep, *outstep, *vert2;

  clipdist = pclipplane->dist;
  pclipnormal = pclipplane->normal;
  
// calc dists
  if (clip_current)
  {
    in = clip_verts[1][0];
    outstep = clip_verts[0][0];
    clip_current = 0;
  }
  else
  {
    in = clip_verts[0][0];
    outstep = clip_verts[1][0];
    clip_current = 1;
  }
  
  instep = in;
  for (i=0 ; i<nump ; i++, instep += sizeof (vec5_t) / sizeof (float))
  {
    dists[i] = DotProduct (instep, pclipnormal) - clipdist;
  }
  
// handle wraparound case
  dists[nump] = dists[0];
  Q_memcpy (instep, in, sizeof (vec5_t));


// clip the winding
  instep = in;
  outcount = 0;

  for (i=0 ; i<nump ; i++, instep += sizeof (vec5_t) / sizeof (float))
  {
    if (dists[i] >= 0)
    {
      Q_memcpy (outstep, instep, sizeof (vec5_t));
      outstep += sizeof (vec5_t) / sizeof (float);
      outcount++;
    }

    if (dists[i] == 0 || dists[i+1] == 0)
      continue;

    if ( (dists[i] > 0) == (dists[i+1] > 0) )
      continue;
      
  // split it into a new vertex
    frac = dists[i] / (dists[i] - dists[i+1]);
      
    vert2 = instep + sizeof (vec5_t) / sizeof (float);
    
    outstep[0] = instep[0] + frac*(vert2[0] - instep[0]);
    outstep[1] = instep[1] + frac*(vert2[1] - instep[1]);
    outstep[2] = instep[2] + frac*(vert2[2] - instep[2]);
    outstep[3] = instep[3] + frac*(vert2[3] - instep[3]);
    outstep[4] = instep[4] + frac*(vert2[4] - instep[4]);

    outstep += sizeof (vec5_t) / sizeof (float);
    outcount++;
  } 
  
  return outcount;
}


/*
================
R_SetupAndDrawSprite
================
*/
void R_SetupAndDrawSprite ()
{
  int     i, nump;
  float   dot, scale, *pv;
  vec5_t    *pverts;
  vec3_t    left, up, right, down, transformed, local;
  emitpoint_t outverts[MAXWORKINGVERTS+1], *pout;

  dot = DotProduct (r_spritedesc.vpn, modelorg);

// backface cull
  if (dot >= 0)
    return;

// build the sprite poster in worldspace
  VectorScale (r_spritedesc.vright, r_spritedesc.pspriteframe->right, right);
  VectorScale (r_spritedesc.vup, r_spritedesc.pspriteframe->up, up);
  VectorScale (r_spritedesc.vright, r_spritedesc.pspriteframe->left, left);
  VectorScale (r_spritedesc.vup, r_spritedesc.pspriteframe->down, down);

  pverts = clip_verts[0];

  pverts[0][0] = r_entorigin[0] + up[0] + left[0];
  pverts[0][1] = r_entorigin[1] + up[1] + left[1];
  pverts[0][2] = r_entorigin[2] + up[2] + left[2];
  pverts[0][3] = 0;
  pverts[0][4] = 0;

  pverts[1][0] = r_entorigin[0] + up[0] + right[0];
  pverts[1][1] = r_entorigin[1] + up[1] + right[1];
  pverts[1][2] = r_entorigin[2] + up[2] + right[2];
  pverts[1][3] = sprite_width;
  pverts[1][4] = 0;

  pverts[2][0] = r_entorigin[0] + down[0] + right[0];
  pverts[2][1] = r_entorigin[1] + down[1] + right[1];
  pverts[2][2] = r_entorigin[2] + down[2] + right[2];
  pverts[2][3] = sprite_width;
  pverts[2][4] = sprite_height;

  pverts[3][0] = r_entorigin[0] + down[0] + left[0];
  pverts[3][1] = r_entorigin[1] + down[1] + left[1];
  pverts[3][2] = r_entorigin[2] + down[2] + left[2];
  pverts[3][3] = 0;
  pverts[3][4] = sprite_height;

// clip to the frustum in worldspace
  nump = 4;
  clip_current = 0;

  for (i=0 ; i<4 ; i++)
  {
    nump = R_ClipSpriteFace (nump, &view_clipplanes[i]);
    if (nump < 3)
      return;
    if (nump >= MAXWORKINGVERTS)
      Sys_Error("R_SetupAndDrawSprite: too many points");
  }

// transform vertices into viewspace and project
  pv = &clip_verts[clip_current][0][0];
  r_spritedesc.nearzi = -999999;

  for (i=0 ; i<nump ; i++)
  {
    VectorSubtract (pv, r_origin, local);
    TransformVector (local, transformed);

    if (transformed[2] < NEAR_CLIP)
      transformed[2] = NEAR_CLIP;

    pout = &outverts[i];
    pout->zi = 1.0 / transformed[2];
    if (pout->zi > r_spritedesc.nearzi)
      r_spritedesc.nearzi = pout->zi;

    pout->s = pv[3];
    pout->t = pv[4];
    
    scale = xscale * pout->zi;
    pout->u = (xcenter + scale * transformed[0]);

    scale = yscale * pout->zi;
    pout->v = (ycenter - scale * transformed[1]);

    pv += sizeof (vec5_t) / sizeof (pv);
  }

// draw it
  r_spritedesc.nump = nump;
  r_spritedesc.pverts = outverts;
  D_DrawSprite ();
}


/*
================
R_GetSpriteframe
================
*/
mspriteframe_t *R_GetSpriteframe (msprite_t *psprite)
{
  mspritegroup_t  *pspritegroup;
  mspriteframe_t  *pspriteframe;
  int       i, numframes, frame;
  float     *pintervals, fullinterval, targettime, time;

  frame = currententity->frame;

  if ((frame >= psprite->numframes) || (frame < 0))
  {
    Con_Printf ("R_DrawSprite: no such frame %d\n", frame);
    frame = 0;
  }

  if (psprite->frames[frame].type == SPR_SINGLE)
  {
    pspriteframe = psprite->frames[frame].frameptr;
  }
  else
  {
    pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
    pintervals = pspritegroup->intervals;
    numframes = pspritegroup->numframes;
    fullinterval = pintervals[numframes-1];

    time = cl.time + currententity->syncbase;

  // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values
  // are positive, so we don't have to worry about division by 0
    targettime = time - ((int)(time / fullinterval)) * fullinterval;

    for (i=0 ; i<(numframes-1) ; i++)
    {
      if (pintervals[i] > targettime)
        break;
    }

    pspriteframe = pspritegroup->frames[i];
  }

  return pspriteframe;
}


/*
================
R_DrawSprite
================
*/
void R_DrawSprite (void)
{
  int       i;
  msprite_t   *psprite;
  vec3_t      tvec;
  float     dot, angle, sr, cr;

  psprite = currententity->model->cache.data;

  r_spritedesc.pspriteframe = R_GetSpriteframe (psprite);

  sprite_width = r_spritedesc.pspriteframe->width;
  sprite_height = r_spritedesc.pspriteframe->height;

// TODO: make this caller-selectable
  if (psprite->type == SPR_FACING_UPRIGHT)
  {
  // generate the sprite's axes, with vup straight up in worldspace, and
  // r_spritedesc.vright perpendicular to modelorg.
  // This will not work if the view direction is very close to straight up or
  // down, because the cross product will be between two nearly parallel
  // vectors and starts to approach an undefined state, so we don't draw if
  // the two vectors are less than 1 degree apart
    tvec[0] = -modelorg[0];
    tvec[1] = -modelorg[1];
    tvec[2] = -modelorg[2];
    VectorNormalize (tvec);
    dot = tvec[2];  // same as DotProduct (tvec, r_spritedesc.vup) because
            //  r_spritedesc.vup is 0, 0, 1
    if ((dot > 0.999848) || (dot < -0.999848))  // cos(1 degree) = 0.999848
      return;
    r_spritedesc.vup[0] = 0;
    r_spritedesc.vup[1] = 0;
    r_spritedesc.vup[2] = 1;
    r_spritedesc.vright[0] = tvec[1];
                // CrossProduct(r_spritedesc.vup, -modelorg,
    r_spritedesc.vright[1] = -tvec[0];
                //              r_spritedesc.vright)
    r_spritedesc.vright[2] = 0;
    VectorNormalize (r_spritedesc.vright);
    r_spritedesc.vpn[0] = -r_spritedesc.vright[1];
    r_spritedesc.vpn[1] = r_spritedesc.vright[0];
    r_spritedesc.vpn[2] = 0;
          // CrossProduct (r_spritedesc.vright, r_spritedesc.vup,
          //  r_spritedesc.vpn)
  }
  else if (psprite->type == SPR_VP_PARALLEL)
  {
  // generate the sprite's axes, completely parallel to the viewplane. There
  // are no problem situations, because the sprite is always in the same
  // position relative to the viewer
    for (i=0 ; i<3 ; i++)
    {
      r_spritedesc.vup[i] = vup[i];
      r_spritedesc.vright[i] = vright[i];
      r_spritedesc.vpn[i] = vpn[i];
    }
  }
  else if (psprite->type == SPR_VP_PARALLEL_UPRIGHT)
  {
  // generate the sprite's axes, with vup straight up in worldspace, and
  // r_spritedesc.vright parallel to the viewplane.
  // This will not work if the view direction is very close to straight up or
  // down, because the cross product will be between two nearly parallel
  // vectors and starts to approach an undefined state, so we don't draw if
  // the two vectors are less than 1 degree apart
    dot = vpn[2]; // same as DotProduct (vpn, r_spritedesc.vup) because
            //  r_spritedesc.vup is 0, 0, 1
    if ((dot > 0.999848) || (dot < -0.999848))  // cos(1 degree) = 0.999848
      return;
    r_spritedesc.vup[0] = 0;
    r_spritedesc.vup[1] = 0;
    r_spritedesc.vup[2] = 1;
    r_spritedesc.vright[0] = vpn[1];
                    // CrossProduct (r_spritedesc.vup, vpn,
    r_spritedesc.vright[1] = -vpn[0]; //  r_spritedesc.vright)
    r_spritedesc.vright[2] = 0;
    VectorNormalize (r_spritedesc.vright);
    r_spritedesc.vpn[0] = -r_spritedesc.vright[1];
    r_spritedesc.vpn[1] = r_spritedesc.vright[0];
    r_spritedesc.vpn[2] = 0;
          // CrossProduct (r_spritedesc.vright, r_spritedesc.vup,
          //  r_spritedesc.vpn)
  }
  else if (psprite->type == SPR_ORIENTED)
  {
  // generate the sprite's axes, according to the sprite's world orientation
    AngleVectors (currententity->angles, r_spritedesc.vpn,
            r_spritedesc.vright, r_spritedesc.vup);
  }
  else if (psprite->type == SPR_VP_PARALLEL_ORIENTED)
  {
  // generate the sprite's axes, parallel to the viewplane, but rotated in
  // that plane around the center according to the sprite entity's roll
  // angle. So vpn stays the same, but vright and vup rotate
    angle = currententity->angles[ROLL] * (M_PI*2 / 360);
    sr = sin(angle);
    cr = cos(angle);

    for (i=0 ; i<3 ; i++)
    {
      r_spritedesc.vpn[i] = vpn[i];
      r_spritedesc.vright[i] = vright[i] * cr + vup[i] * sr;
      r_spritedesc.vup[i] = vright[i] * -sr + vup[i] * cr;
    }
  }
  else
  {
    Sys_Error ("R_DrawSprite: Bad sprite type %d", psprite->type);
  }

  R_RotateSprite (psprite->beamlength);

  R_SetupAndDrawSprite ();
}

