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

                     ray trace module

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

#include <math.h>
#include "qrt.h"

/* #define RAYDEBUG 1 */

OBJ_PTR new_line(), new_obj();


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

  Diffuse color module. Also compute specular reflections
  here for efficiency sake - only one lamp eneumerator
  needed that way.

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

DiffColor(color,cinfo,norm,loc,oline)
  SVECT_PTR color;
  VECT_PTR norm,loc;
  CINFO_PTR cinfo;
  OBJ_PTR oline;
{
  OBJ_PTR lamp, line, CurrObj, Ray_Hit();
  float t;
  register float dist, t1,t2;
  VECTOR refl, atten;

  line=new_line();
  VectEQ(&(line->loc),loc);
  lamp=THEWORLD.lamps;

  while (lamp!=NULL) {

#   ifdef ROBUST
      if (lamp->type!=LAMP) Error(INTERNAL_ERROR,601);
#   endif

    /* find dir to lamp */
    VecSubtract(&(line->vect1),&(lamp->loc),loc);

    CurrObj=NULL;
    atten.x = atten.y = atten.z = 1.00;

    if (def.shadow==TRUE) {

      /* set light attenuation factor */

      CurrObj=Ray_Hit(THEWORLD.stack,line,&t,TRUE,TRUE,&atten);

      THEWORLD.to_lamp++;
    }

    if (CurrObj==NULL) {                         /* hit nothing ? */

      dist = DotProd((line->vect1),(line->vect1));
      Normalize(&(line->vect1));

      if (cinfo->diff.r>0 || cinfo->diff.g>0 || cinfo->diff.b>0) {
        t1 = DotProd((*norm),(line->vect1));

        if (t1>0) {
          t2 = lamp->vect1.y*t1/sqrt(dist);

          color->r+=(short)((lamp->cinfo.amb.r) * atten.x *
                    t2*(cinfo->diff.r)/CNUM);
          color->g+=(short)((lamp->cinfo.amb.g) * atten.y *
                    t2*(cinfo->diff.g)/CNUM);
          color->b+=(short)((lamp->cinfo.amb.b) * atten.z *
                    t2*(cinfo->diff.b)/CNUM);
        }
      }

      if (cinfo->sreflect>0) {                 /* specular */
        line->vect1.x = -line->vect1.x;        /* reverse line */
        line->vect1.y = -line->vect1.y;
        line->vect1.z = -line->vect1.z;
        Reflect(&refl,&(line->vect1),norm);
        t1 = -DotProd(refl,(oline->vect1));
        if (t1>0) {

          t2 = pow(t1,cinfo->sreflect)*        /* this is slow !! */
               lamp->vect1.y/sqrt(dist);

          t2 *= (float)cinfo->reflect/(float)CNUM;

          color->r+=(short)(t2 * atten.x * (lamp->cinfo.amb.r));
          color->g+=(short)(t2 * atten.y * (lamp->cinfo.amb.g));
          color->b+=(short)(t2 * atten.z * (lamp->cinfo.amb.b));
        }
      }
    }
    lamp=lamp->nextobj;
  }

# ifdef RAYDEBUG
    printf("DIFFCOLOR: r=%d, g=%d, b=%d\n",
            color->r, color->g, color->b);
# endif

  free(line);
}


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

                     Ambient color module

   Really simple - just add the color, no questions asked.

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

AmbColor(color, cinfo, norm, loc)
  SVECT_PTR color;
  VECT_PTR norm, loc;
  CINFO_PTR cinfo;
{
  color->r+=(short)((int)cinfo->amb.r*(int)cinfo->diff.r/CNUM);
  color->g+=(short)((int)cinfo->amb.g*(int)cinfo->diff.g/CNUM);
  color->b+=(short)((int)cinfo->amb.b*(int)cinfo->diff.b/CNUM);

# ifdef RAYDEBUG
    printf("AMBCOLOR: added r=%d, g=%d, b=%d\n",
            cinfo->amb.r,cinfo->amb.g,cinfo->amb.b);
# endif
}


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

                 Transmitted color module

 This is hairy and slow - bends ray around normal vector
 by ratio of indicies of refraction.  line->flag=TRUE
 if we are inside a piece of glass - this means glass must
 have a simple structure.

 10 Aug 88 - Fixed TransColor so that it works.  The
 original algorithm came from a book, and I had some
 problems getting it to work.  This one came from my head,
 and seems to work ok.

 11 Aug 88 - Added density effects (light is reduced
 more by thicker glass).

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

/* #define TESTTRANS TRUE */

TransColor(color,cinfo,norm,loc,line,inmult)
  SVECT_PTR color;
  VECT_PTR norm,loc;
  OBJ_PTR line;
  CINFO_PTR cinfo;
  float inmult;
{
  OBJ_STRUCT newline;
  SVECTOR    col1;
  VECTOR     toadd, negvect1, distance;
  float      index2, multiplier, side1;
  int        maxtrans;

  /* these vars are used to keep track of the distance
     traveled through the glass so we can attenuate the
     light accordingly.                                 */

  static     VECTOR prevpos;
  VECTOR     displacement;
  SVECTOR    attenuation;
  float      glassdist;

# ifdef TRANSDEBUG
    printf("TRANSCOLOR:\n");
# endif
# ifdef RAYDEBUG
    printf("TRANSCOLOR:\n");
# endif

  if ((cinfo->trans.r < def.ithreshold) &&
      (cinfo->trans.g < def.ithreshold) &&
      (cinfo->trans.b < def.ithreshold)) return;

# ifdef ROBUST
    if (line->type!=LINE) Error(INTERNAL_ERROR,602);
    if ((THEWORLD.globindex == 0) ||
        (cinfo->index == 0))
          Error(ZERO_INDEX,603);
# endif

  if (line->flag) {

    /* inside an object going out ? */
    index2 = cinfo->index/THEWORLD.globindex;

    /* find offset from prev position */
    VecSubtract(&displacement,loc,&prevpos);

    /* measure distance */
    glassdist = sqrt(DotProd(displacement,displacement));

  } else {

    /* outside an object going in ? */
    index2 = THEWORLD.globindex/cinfo->index;
    /* remember this position */
    VectEQ(&prevpos,loc);
  }

  /* doesn't currently use new_line() call */
  newline.type = LINE;
  newline.child = newline.nextobj = NULL;

  /* this ray starts from where we hit glass */
  VectEQ(&(newline.loc),loc);
  VectEqZero(&toadd);

  VectNegate(&negvect1,&(line->vect1));
  side1 = DotProd((*norm),negvect1);

  VectAddMult(&toadd,-side1,norm,(float)1,&negvect1);

  VectScale(&toadd,(1-index2));

  VectorAdd(&(newline.vect1),&(line->vect1),&toadd);

  Normalize(&(newline.vect1));

  maxtrans = MAX(MAX(cinfo->trans.r,cinfo->trans.g),
                     cinfo->trans.b);

  /* if we were inside, now we're out */
  newline.flag = !(line->flag);

# ifdef TESTTRANS

    if (line->flag) printf("line TRUE\n");
    if (newline.flag) printf("newline TRUE\n");

    printf("Trans = %d %d %d\n",cinfo->trans.r,
                                cinfo->trans.g,
                                cinfo->trans.b);

    printf("index2 = %f\n",index2);
    printf("side1  = %f\n",side1);

    printf("Dot(toadd,norm) = %f\n",
           DotProd(toadd,(*norm)));

    printf("Dot(norm,negvect1) = %f\n",
           DotProd((*norm),negvect1));

    printf("Dot(-norm,newvect) = %f\n",
           -DotProd((*norm),newline.vect1));

    printf("inmult, maxtrans = %f %d\n",inmult,maxtrans);
# endif

  multiplier = inmult * (float)maxtrans/(float)CNUM;
  Ray_Trace(&newline,&col1,multiplier);
  THEWORLD.refl_trans++;

# ifdef TESTTRANS
    printf("Col1 = %d %d %d\n",col1.r,col1.g,col1.b);
# endif

  color->r += col1.r;

  color->g += col1.g;

  color->b += col1.b;

  if (line->flag) {          /* density effects? */

    /* density uses x,y,z not r,g,b cuz its a floating
       point vector.                                   */

    attenuation.r = (int)(cinfo->density.x * glassdist *
                         (float)(color->r));

    attenuation.g = (int)(cinfo->density.y * glassdist *
                         (float)(color->g));

    attenuation.b = (int)(cinfo->density.z * glassdist *
                         (float)(color->b));

    /* don't remove more than original intensity! */

    color->r -= MIN(attenuation.r,color->r);
    color->g -= MIN(attenuation.g,color->g);
    color->b -= MIN(attenuation.b,color->b);

  }
}


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

                   Reflective color module

      Bounce ray off object and recursively ray trace.

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

ReflectColor(color,cinfo,norm,loc,line,inmult)
  SVECT_PTR color;
  VECT_PTR  norm,loc;
  OBJ_PTR   line;
  CINFO_PTR cinfo;
  float inmult;
{
  SVECTOR col1;
  OBJ_STRUCT newline;
  float multiplier;
  int maxmirror;

# ifdef ROBUST
    if (line->type!=LINE) Error(INTERNAL_ERROR,604);
# endif

  if ((cinfo->mirror.r < def.ithreshold) &&
      (cinfo->mirror.g < def.ithreshold) &&
      (cinfo->mirror.b < def.ithreshold)) return;

  newline.type = LINE;

  newline.child = newline.nextobj = NULL;

  VectEQ(&(newline.loc),loc);
  Reflect(&(newline.vect1),&(line->vect1),norm);

  maxmirror = MAX(MAX(cinfo->mirror.r,cinfo->mirror.g),
              cinfo->mirror.b);

  multiplier = inmult * (float)maxmirror/CNUM;

  Ray_Trace(&newline,&col1,multiplier);
  THEWORLD.refl_trans++;

  color->r+=(int)col1.r*(int)cinfo->mirror.r/CNUM;

  color->g+=(int)col1.g*(int)cinfo->mirror.g/CNUM;

  color->b+=(int)col1.b*(int)cinfo->mirror.b/CNUM;

# ifdef RAYDEBUG
    printf("REFLECTCOLOR:\n");
    printf("  col1 = %d %d %d\n",col1.r,col1.g,col1.b);
# endif

}


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

   Computes sky color given line - interpolate between
   horizon and zenith to find color (user should dither
   the explitive out of the sky to compensate for lack
   of color resolution.

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

SkyColor(line,color)
  OBJ_PTR line;
  SVECT_PTR color;
{
  float length, horiz, zenith;

# ifdef ROBUST
    if (line->type!=LINE) Error(INTERNAL_ERROR,605);
# endif

  if (THEWORLD.sky==NULL) return;

# ifdef ROBUST
    if (THEWORLD.sky->type != SKY)
      Error(INTERNAL_ERROR,606);
# endif

  length = DotProd(line->vect1,line->vect1);

  zenith = sqr(line->vect1.y)/length;
  horiz  = (sqr(line->vect1.x)+sqr(line->vect1.z))/length;

  color->r += (short)(zenith*THEWORLD.skycolor_zenith.r+
                      horiz *THEWORLD.skycolor_horiz.r);

  color->g += (short)(zenith*THEWORLD.skycolor_zenith.g+
                      horiz *THEWORLD.skycolor_horiz.g);

  color->b += (short)(zenith*THEWORLD.skycolor_zenith.b+
                      horiz *THEWORLD.skycolor_horiz.b);

  Dither(color,&((THEWORLD.sky)->cinfo));


# ifdef RAYDEBUG
    printf("SKYCOLOR:\n");
# endif

}


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

  Color dithering rountine - negative dither number will
  dither all three colors together - positive with perform
  separate color dithering on all colors.

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

#define MINCOL 10
Dither(color,cinfo)
  SVECT_PTR color;
  CINFO_PTR cinfo;
{
  register int r,g,b;
  int PsRand();

  if (cinfo->dither==0) return;

  if (cinfo->dither>0) {
    r=PsRand(); g=PsRand(); b=PsRand();

    if (color->r<MINCOL) r=IABS(r);
    if (color->g<MINCOL) g=IABS(g);
    if (color->b<MINCOL) b=IABS(b);

    color->r += (short)(r*cinfo->dither/CNUM);
    color->g += (short)(g*cinfo->dither/CNUM);
    color->b += (short)(b*cinfo->dither/CNUM);
  } else {
    r=PsRand();
    if ((color->r+color->g+color->b)>(3*MINCOL)) r=IABS(r);

    color->r += (short)(r*cinfo->dither/CNUM);
    color->g += (short)(r*cinfo->dither/CNUM);
    color->b += (short)(r*cinfo->dither/CNUM);
  }
}


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

   Returns pointer to object hit by ray.
   Parameters:
     CurrObj = root of object tree
     line    = light ray
     MinT    = parameter T for line/obj intersection
     sflag   = if TRUE, stop on first intersection
     fflag   = ALWAYS pass TRUE here.
     pos1    = position vector for object relative coords
     pos2    = position vector for object relative coords

  - changed 11 jun 88 to fix shadow routine -

  - changed 12 aug 88 to add light attenuation by glass.
            this is not done correctly, but is better
            than nothing.

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

OBJ_PTR Ray_Hit(CurrObj,line,MinT,sflag,fflag,atten)
  OBJ_PTR line, CurrObj;
  float *MinT;
  short sflag,fflag;
  VECT_PTR atten;           /* light attenuation by glass */
{
  static OBJ_PTR MinObj;
  VECTOR loc;
  OBJ_PTR obj;
  short collision;
  static short stop;
  float t;

  obj = CurrObj;

  if (fflag) {

#   ifdef ROBUST
      if (line->type!=LINE) Error(INTERNAL_ERROR,607);
#   endif

    *MinT=3e30;
    MinObj=NULL;
    stop=FALSE;
  }

  while (obj!=NULL && !stop) {         /* check for object collisions */

    collision =
      (*(ObjData[obj->type].ColTest))(line,obj,&t);

    if (collision && (obj->remove != NULL)) {
      FindPos(&loc,line,t);
      if (Find_Color(obj,obj->remove,&loc,NULL, 1.0, 1.0))
        collision = FALSE;
    }

    THEWORLD.intersect_tests++;

    if (collision && (t>SMALL)) {      /* did we hit something ? */

#     ifdef RAYDEBUG
        printf("RAY_HIT: Collision t=%f\n",t);
#     endif

      if (obj->type!=BBOX) {           /* if not BBOX */
        THEWORLD.ray_intersects++;
        if (sflag && t<1) {

          /* did we hit a transparent object? */
          /* PS - this is not right - fix it later */

          if ((obj->cinfo.trans.r < def.ithreshold) &&
              (obj->cinfo.trans.g < def.ithreshold) &&
              (obj->cinfo.trans.b < def.ithreshold)) {

            stop   = TRUE;
            MinObj = obj;
            return(obj);

          } else {

            /* attenuate light if transparent object.  This is
               REALLY screwy and not at all correct, but it is
               the only simple way to do it.
            */

            if (atten != NULL) {
              atten->x *= sqr(((float)obj->cinfo.trans.r) /
                          (float)CNUM);
              atten->y *= sqr(((float)obj->cinfo.trans.g) /
                          (float)CNUM);
              atten->z *= sqr(((float)obj->cinfo.trans.b) /
                          (float)CNUM);
            }
          }
        }
        if ((!sflag) && (t<*MinT)) {   /* nearest collision ? */
          *MinT  = t;                  /* if so, save it */
          MinObj = obj;
        }
      } else {                         /* is bbox hit */
        THEWORLD.bbox_intersects++;
        Ray_Hit(obj->child,line,MinT,sflag,FALSE,atten);
      }
    }
    obj=obj->nextobj;
  }

  return(MinObj);
}


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

  Performs ray tracing in line, fills color structure.
  Multiplier is a number by which the color output will
  be mulplied (0..1) so that we can tell when its useless
  to continue recursivly tracing rays.

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

int Ray_Trace(line,color,multiplier)
  OBJ_PTR line;
  SVECT_PTR color;
  float multiplier;
{
  float MinT, divisor;
  OBJ_PTR MinObj;
  CINFO cinfo;
  VECTOR MinLoc, MinNorm;

# ifdef ROBUST
    if (line->type!=LINE) Error(INTERNAL_ERROR,608);
# endif

  color->r = color->g = color->b = 0;

  /* check here if so little light is added that it doesn't matter */
  if (multiplier < def.threshold) return(FALSE);

  MinObj=Ray_Hit(THEWORLD.stack,line,&MinT,FALSE,TRUE,NULL);

  if (MinObj!=NULL) {
    FindPos(&MinLoc,line,MinT);

#   ifdef RAYDEBUG
      printf("RAY_TRACE: Collision, x,y,z = %f %f %f\n",
             MinLoc.x, MinLoc.y, MinLoc.z);
#   endif

    /*  Find object normal vector */

    (*(ObjData[MinObj->type].FindNorm))(&MinNorm,MinObj,&MinLoc);

    if (DotProd(MinNorm,(line->vect1)) >0) {   /* reverse   */
      MinNorm.x = -MinNorm.x;                  /* normal if */
      MinNorm.y = -MinNorm.y;                  /* necessary */
      MinNorm.z = -MinNorm.z;
    }
                                               /* Find colinfo */
    Find_Color(MinObj,
               MinObj->pattern,
               &MinLoc,
               &cinfo,
               MinObj->xmult,
               MinObj->ymult
              );

    AmbColor(color,&cinfo,&MinNorm,&MinLoc);
    DiffColor(color,&cinfo,&MinNorm,&MinLoc,line);
    TransColor(color,&cinfo,&MinNorm,&MinLoc,line,multiplier);
    ReflectColor(color,&cinfo,&MinNorm,&MinLoc,line,multiplier);
    Dither(color,&cinfo);

  } else {
    SkyColor(line,color);
  }

  if (color->r > CNUM || color->g > CNUM || color->b > CNUM) {
    divisor = (float)MAX(MAX(color->r,color->g),color->b) /
              (float)CNUM;

    color->r = (float)color->r/divisor;
    color->g = (float)color->g/divisor;
    color->b = (float)color->b/divisor;
  }

  return(MinObj!=NULL);
}


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

     Generates line for a given x,y pixel position
     This fn needs a little work, as currently it
     produces some distortion around the edge of
     the screen.

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

PixelLine(x, y, line)           /* generate equation for a line */
  int x,y;
  OBJ_PTR line;
{
  register float xf, yf;

# ifdef ROBUST
    if (line->type!=LINE) Error(INTERNAL_ERROR,609);
# endif

  xf=((float)(CENTERX-x))/(THEWORLD.flength*ASPECT);
  yf=((float)(CENTERY-y))/(THEWORLD.flength);

  VectEQ(&(line->loc),&(THEWORLD.observer->loc));
  VectEQ(&(line->vect1),&(THEWORLD.observer->vect1));
  VectAddMult(&(line->vect1),xf,&(THEWORLD.obsright),
                             yf,&(THEWORLD.obsup));

  line->flag = FALSE;           /* redundant? */
}


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

                    Not yet implimented

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

Region_Trace(xbegin,xend,ybegin,yend)
  int xbegin, xend, ybegin, yend;
{
  int x,y;
}


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

   Sends scan line color data to file - file format is:

   BYTE 0 : low  byte of x resolution
   BYTE 1 : high byte of x resolution
   BYTE 2 : low  byte of y resolution
   BYTE 3 : high byte of y resolution

   Then, for each scan line, the line number is listed
   (2 bytes), followed by the r,g, and b bytes.

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

Dump_Line(lineno,r,g,b)
  short r[],g[],b[];
  int lineno;
{
  int x;

#ifndef RAYDEBUG

  /** line number **/

  fputc(((unsigned char)(lineno&(0xff))),THEWORLD.filept);
  fputc(((unsigned char)(lineno>>8)),    THEWORLD.filept);

  for (x=0; x<XSIZE; x++) {
    fputc((unsigned char)r[x],THEWORLD.filept);
  }
  for (x=0; x<XSIZE; x++) {
    fputc((unsigned char)g[x],THEWORLD.filept);
  }
  for (x=0; x<XSIZE; x++) {
    fputc((unsigned char)b[x],THEWORLD.filept);
  }
#endif
}


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

                  Ray trace whole screen

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

Screen_Trace() {
  int x,y;
  short rbyte[XSIZE], gbyte[XSIZE], bbyte[XSIZE];
  SVECTOR color;
  OBJ_PTR line;

  line=new_line();

# ifdef RAYDEBUG
    printf("SCREEN_TRACE: line:\n");
    printf("First,Last = %d %d\n",
           THEWORLD.first_scan,THEWORLD.last_scan);
# endif

  for (y=THEWORLD.first_scan; y<=THEWORLD.last_scan; y++) {

    for (x=0; x<XSIZE; x++) {

      PixelLine(x,y,line);

      Ray_Trace(line,&color,(float)1.0);
      THEWORLD.primary_traced++;

#     ifdef RAYDEBUG
        printf("SCREEN_TRACE: r,g,b = %d %d %d\n",
               color.r,color.g,color.b);
#     endif

      rbyte[x] = color.r;
      gbyte[x] = color.g;
      bbyte[x] = color.b;

#     ifdef RAYDEBUG
        printf("SCREEN_TRACE: r=%d, g=%d, b=%d\n",
               color.r,color.g,color.b);
#     endif
    }

    Dump_Line(y,rbyte,gbyte,bbyte);
  }
  free(line);
}
