/* 
 * shade.c - This file contains the functions that perform surface shading.
 *
 *  $Id: shade.c,v 1.62 2001/03/01 20:07:34 johns Exp $
 */

#include "machine.h"
#include "types.h"
#include "macros.h"
#include "threads.h"
#include "light.h"
#include "intersect.h"
#include "vector.h"
#include "trace.h"
#include "shade.h"


/*
 * Lowest Quality Shader - Returns the raw color of an object.
 *
 */

color lowest_shader(ray * incident) {
  int numints;
  object * obj;
  flt t;

  numints=closest_intersection(&t, &obj, incident);
                /* find the number of intersections */
                /* and return the closest one.      */

  if (numints < 1) {
    /* if there weren't any object intersections then return the */
    /* background color for the pixel color.                     */
    return incident->scene->background;
  }

  return obj->tex->col;
}


/*
 * Low Quality Shader - Returns raw texture color of objects hit, nothing else.
 *
 */

color low_shader(ray * incident) {
  int numints;
  object * obj;
  vector hit;
  flt t;

  numints=closest_intersection(&t, &obj, incident);
                /* find the number of intersections */
                /* and return the closest one.      */

  if (numints < 1) {
    /* if there weren't any object intersections then return the */
    /* background color for the pixel color.                     */
    return incident->scene->background;
  }

  RAYPNT(hit, (*incident), t) /* find the point of intersection from t */
  incident->opticdist = FHUGE; 
  return obj->tex->texfunc(&hit, obj->tex, incident);
}



/*
 * Medium Quality Shader - Includes a subset of the rendering features
 *
 */

color medium_shader(ray * incident) {
  color col, diffuse, phongcol; 
  vector N, L, hit;
  flt inten, t, Llen;
  object * obj;
  int numints;
  point_light * li;
  list * cur;

  numints=closest_intersection(&t, &obj, incident);  
		/* find the number of intersections */
                /* and return the closest one.      */

  if (numints < 1) {         
    /* if there weren't any object intersections then return the */
    /* background color for the pixel color.                     */
    return incident->scene->background;
  }

  if (obj->tex->islight) {  /* if the current object is a light, then we  */
    return obj->tex->col;   /* will only use the objects ambient color    */
  }

  RAYPNT(hit, (*incident), t)       /* find the point of intersection from t */ 
  incident->opticdist += t;
  obj->methods->normal(obj, &hit, incident, &N);  /* find the surface normal */

  /* Flip surface normal to point toward the viewer if necessary */
  if (VDot(&N, &(incident->d)) > 0.0)  {
    N.x=-N.x;
    N.y=-N.y;
    N.z=-N.z;
  }

  /* execute the object's texture function */
  col = obj->tex->texfunc(&hit, obj->tex, incident); 

  diffuse.r = 0.0; 
  diffuse.g = 0.0; 
  diffuse.b = 0.0; 
  phongcol = diffuse;

  if ((obj->tex->diffuse > MINCONTRIB) || (obj->tex->phong > MINCONTRIB)) {  

    cur = incident->scene->lightlist;
    while (cur != NULL) {           /* loop for light contributions */
      li=(point_light *) cur->item; /* set li=to the current light  */
      VSUB(li->ctr, hit, L)         /* find the light vector        */

      /* calculate the distance to the light from the hit point */
      Llen = sqrt(L.x*L.x + L.y*L.y + L.z*L.z) + EPSILON;

      L.x /= Llen; /* normalize the light direction vector */
      L.y /= Llen;
      L.z /= Llen;

      VDOT(inten, N, L)             /* light intensity              */
      inten *= li->attenuationfunc(li, Llen);
      inten *= li->spotfunc(li, &L);

      /* add in diffuse lighting for this light if we're facing it */ 
      if (inten > MINCONTRIB) {            
        /* XXX now that opacity is in the code, have to be more careful */
        ColorAddS(&diffuse, &li->tex->col, inten);

        /* phong type specular highlights */
        if (obj->tex->phong > MINCONTRIB) {
          flt phongval;
          phongval = shade_phong_blinn(incident, &N, &L, obj->tex->phongexp); 
          if (obj->tex->phongtype) 
            ColorAddS(&phongcol, &col, phongval * obj->tex->phong);
          else
            ColorAddS(&phongcol, &(li->tex->col), phongval * obj->tex->phong);
        }
      }  

      cur = cur->next;
    } 
  }

  ColorScale(&diffuse, obj->tex->diffuse);

  col.r *= (diffuse.r + obj->tex->ambient); /* do a product of the */
  col.g *= (diffuse.g + obj->tex->ambient); /* diffuse intensity with  */
  col.b *= (diffuse.b + obj->tex->ambient); /* object color + ambient  */

  if (obj->tex->phong > MINCONTRIB) {
    ColorAccum(&col, &phongcol);
  }

  /* spawn reflection rays if necessary */
  /* note: this will overwrite the old intersection list */
  if (obj->tex->specular > MINCONTRIB) {    
    color specol;
    specol = shade_reflection(incident, &hit, &N, obj->tex->specular);
    ColorAccum(&col, &specol);
  }

  /* spawn transmission rays / refraction */
  /* note: this will overwrite the old intersection list */
  if (obj->tex->opacity < (1.0 - MINCONTRIB)) {      
    color transcol;
    transcol = shade_transmission(incident, &hit, 1.0 - obj->tex->opacity);
    ColorAccum(&col, &transcol);
  }

  return col;    /* return the color of the shaded pixel... */
}



/*
 * Full Quality Shader - Includes all possible rendering features
 *
 */

color full_shader(ray * incident) {
  color col, diffuse, phongcol; 
  vector N, L, hit;
  ray shadowray;
  flt inten, t, Llen;
  object * obj;
  int numints;
  point_light * li;
  list * cur;

  numints=closest_intersection(&t, &obj, incident);  
		/* find the number of intersections */
                /* and return the closest one.      */

  if (numints < 1) {         
    /* if there weren't any object intersections then return the */
    /* background color for the pixel color.                     */
    return incident->scene->background;
  }

  if (obj->tex->islight) {  /* if the current object is a light, then we  */
    return obj->tex->col;   /* will only use the objects ambient color    */
  }

  RAYPNT(hit, (*incident), t)       /* find the point of intersection from t */ 
  incident->opticdist += t;
  obj->methods->normal(obj, &hit, incident, &N);  /* find the surface normal */

  /* Flip surface normal to point toward the viewer if necessary */
  if (VDot(&N, &(incident->d)) > 0.0)  {
    N.x=-N.x;
    N.y=-N.y;
    N.z=-N.z;
  }

  /* execute the object's texture function */
  col = obj->tex->texfunc(&hit, obj->tex, incident); 

  diffuse.r = 0.0; 
  diffuse.g = 0.0; 
  diffuse.b = 0.0; 
  phongcol = diffuse;

  if ((obj->tex->diffuse > MINCONTRIB) || (obj->tex->phong > MINCONTRIB)) {  
    cur = incident->scene->lightlist;
    while (cur != NULL) {           /* loop for light contributions */
      li=(point_light *) cur->item; /* set li=to the current light  */
      VSUB(li->ctr, hit, L)         /* find the light vector        */

      /* calculate the distance to the light from the hit point */
      Llen = sqrt(L.x*L.x + L.y*L.y + L.z*L.z) + EPSILON;

      L.x /= Llen; /* normalize the light direction vector */
      L.y /= Llen;
      L.z /= Llen;

      VDOT(inten, N, L)             /* light intensity              */
      inten *= li->attenuationfunc(li, Llen);
      inten *= li->spotfunc(li, &L);

      /* add in diffuse lighting for this light if we're facing it */ 
      if (inten > MINCONTRIB) {            
        /* test for a shadow */
        shadowray.o   = hit;
        shadowray.d   = L;      
        shadowray.maxdist = Llen;
        shadowray.add_intersection = add_shadow_intersection;
        shadowray.flags = RT_RAY_SHADOW;
        incident->serial++;
        shadowray.serial = incident->serial;
        shadowray.mbox = incident->mbox;
        shadowray.scene = incident->scene;
        reset_intersection(&shadowray);
        intersect_objects(&shadowray);

        if (!shadow_intersection(&shadowray)) {
          /* XXX now that opacity is in the code, have to be more careful */
          ColorAddS(&diffuse, &li->tex->col, inten);

          /* phong type specular highlights */
          if (obj->tex->phong > MINCONTRIB) {
            flt phongval;
            phongval = shade_phong_blinn(incident, &N, &L, obj->tex->phongexp); 
            if (obj->tex->phongtype) 
              ColorAddS(&phongcol, &col, phongval * obj->tex->phong);
            else
              ColorAddS(&phongcol, &(li->tex->col), phongval * obj->tex->phong);
          }
        }
      }  

      cur = cur->next;
    } 
  }

  ColorScale(&diffuse, obj->tex->diffuse);

  col.r *= (diffuse.r + obj->tex->ambient); /* do a product of the */
  col.g *= (diffuse.g + obj->tex->ambient); /* diffuse intensity with  */
  col.b *= (diffuse.b + obj->tex->ambient); /* object color + ambient  */

  if (obj->tex->phong > MINCONTRIB) {
    ColorAccum(&col, &phongcol);
  }

  /* spawn reflection rays if necessary */
  /* note: this will overwrite the old intersection list */
  if (obj->tex->specular > MINCONTRIB) {    
    color specol;
    specol = shade_reflection(incident, &hit, &N, obj->tex->specular);
    ColorAccum(&col, &specol);
  }

  /* spawn transmission rays / refraction */
  /* note: this will overwrite the old intersection list */
  if (obj->tex->opacity < (1.0 - MINCONTRIB)) {      
    color transcol;
    transcol = shade_transmission(incident, &hit, 1.0 - obj->tex->opacity);
    ColorAccum(&col, &transcol);
  }

  return col;    /* return the color of the shaded pixel... */
}


color shade_reflection(ray * incident, vector * hit, vector * N, flt specular) {
  ray specray;
  color col;
  vector R;
 
  VAddS(-2.0 * (incident->d.x * N->x + 
                incident->d.y * N->y + 
                incident->d.z * N->z), N, &incident->d, &R);

  specray.o=*hit; 
  specray.d=R;			       /* reflect incident ray about normal */
  specray.o=Raypnt(&specray, EPSILON); /* avoid numerical precision bugs */
  specray.maxdist = FHUGE;             /* take any intersection */
  specray.opticdist = incident->opticdist;
  specray.add_intersection=add_regular_intersection; /* ray type  */
  specray.depth=incident->depth - 1;   /* go up a level in recursion depth */
  specray.flags = RT_RAY_REGULAR;      /* infinite ray, to start with */
  specray.serial = incident->serial + 1; /* next serial number */
  specray.mbox = incident->mbox; 
  specray.scene=incident->scene;       /* global scenedef info */
  col=trace(&specray);                 /* trace specular reflection ray */ 

  incident->serial = specray.serial;    /* update the serial number */

  ColorScale(&col, specular);

  return col;
}


color shade_transmission(ray * incident, vector * hit, flt trans) {
  ray transray;
  color col;

  transray.o=*hit; 
  transray.d=incident->d;                /* ray continues along incident path */
  transray.o=Raypnt(&transray, EPSILON); /* avoid numerical precision bugs */
  transray.maxdist = FHUGE;              /* take any intersection */
  transray.opticdist = incident->opticdist;
  transray.add_intersection=add_regular_intersection; /* ray type  */
  transray.depth=incident->depth - 1;    /* go up a level in recursion depth */
  transray.flags = RT_RAY_REGULAR;       /* infinite ray, to start with */
  transray.serial = incident->serial + 1; /* update serial number */
  transray.mbox = incident->mbox;
  transray.scene=incident->scene;        /* global scenedef info */
  col=trace(&transray);                  /* trace transmission ray */  

  incident->serial = transray.serial;

  ColorScale(&col, trans);

  return col;
}

#if 1
/*
 * Phong shader, implements specular reflection model
 * using Blinn's halfway vector dotted with the surface normal.
 * This is also the shading model used by OpenGL and Direct3D.
 */
flt shade_phong_blinn(ray * incident, vector * N, vector * L, flt specpower) {
  vector H;   /* Blinn's halfway vector */
  flt inten;  /* calculated intensity   */

  /* since incident ray is negated direction to viewer, we subtract... */
  VSub(L, &incident->d, &H); /* sub. incoming ray dir. from light direction */
  VScale(&H, 0.5);     /* scale by half */
  VNorm(&H);           /* normalize halfway vector */
  inten = VDot(N, &H); /* dot product of halfway vector and surface normal */
  if (inten > 0.0)     
    inten = pow(inten, specpower);
  else 
    inten = 0.0;

  return inten;
} 
#else
/*
 * Phong shader, implements specular reflection model
 * using Blinn's halfway vector dotted with the surface normal.
 * This is also the shading model used by OpenGL and Direct3D.
 * Uses Graphics Gems IV chapter VI.1 algorithm for phong exponent
 * instead of the usual call to pow(). 
 */
flt shade_phong_blinn(ray * incident, vector * N, vector * L, flt specpower) {
  vector H;   /* Blinn's halfway vector */
  flt inten;  /* calculated intensity   */

  /* since incident ray is negated direction to viewer, we subtract... */
  VSub(L, &incident->d, &H); /* sub. incoming ray dir. from light direction */
  VScale(&H, 0.5);     /* scale by half */
  VNorm(&H);           /* normalize halfway vector */
  inten = VDot(N, &H); /* dot product of halfway vector and surface normal */
  if (inten > 0.0)    
    inten = inten / (specpower - (specpower * inten) + inten);
  else 
    inten = 0.0;

  return inten;
} 
#endif

/*
 * Phong shader, implements a Phong specular reflection model
 * using the reflection vector about the surface normal, dotted
 * with the direction to the viewer.  This is the "classic" phong
 */
flt shade_phong(ray * incident, vector * N, vector * L, flt specpower) {
  vector R;   /* reflection vector      */
  vector V;   /* direction to viewpoint */
  vector LL;  /* reverse direction to light     */
  flt inten;  /* calculated intensity   */

  LL = *L;
  VScale(&LL, -1.0);
  VAddS(-2.0 * (LL.x * N->x + 
                LL.y * N->y + 
                LL.z * N->z), N, &LL, &R);

  V = incident->d;
  VScale(&V, -1.0);
  VNorm(&R);            /* normalize reflection vector */
  inten = VDot(&V, &R); /* dot product of halfway vector and surface normal */
  if (inten > 0.0)     
    inten = pow(inten, specpower);
  else 
    inten = 0.0;

  return inten;
} 


