/* *********************************************************************
   *                                                                   *
   *  NAME     : Mariusz Zaczek          PROGRAM : MP6                 *
   *  NET ID   : zaczek                  COURSE  : CS318 - Fall 1999   *
   *  DUE DATE : Dec. 10,1999                                          *
   *                                                                   *
   *  PURPOSE  : The purpose of this MP is to develop a Ray Tracer     *
   *                                                                   *
   *  INPUTS   : MOUSE & KEYBOARD INPUTS:                              *
   *             q or Q             - used to quit the program         *
   *                                                                   *
   ********************************************************************* */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>

/* Defines */
#define TRUE  1
#define FALSE 0

#define TOLERANCE 0.000001   /* Tolerance used for abs. val comparisons */
#define MAXDEPTH  5          /* Max recursion depth */
#define LARGE_NUM 99999.99   /* A large number */

/* Global variables for screen width and height*/
GLint WinWidth = 600;
GLint WinHeight = 600;

/* Structures */
typedef struct          /* Point structure */
{
  GLdouble x, y;      
} Point;

typedef struct          /* Vector structure used for points as well */
{
  GLdouble x, y, z;
} Vector;

typedef struct          /* Color struct with R, G, B values.        */
{
  GLdouble R, G, B;
} ColorVector;

typedef struct          /* Sphere struct with center,radius,color   */
{
  Vector center;
  GLdouble radius;
  ColorVector ka,
              kd,
              ks;
} Sphere;

typedef struct          /* Ray struct containing both endpoints.    */
{
  Vector orig,
         u;
} RayVector;


/* enums */
typedef enum { none = 0, plane, sphere1, sphere2 } Object;


/* Variable Declarations */
GLdouble ReflectionCoefficient = 0.5;

Vector PlaneNormal = {  0.0,  0.0,  1.0 },        /* Grid Plane Normal */
       LightCenter = {  2.0, -6.0,  5.0 },        /* Light location    */   
       EyeCenter   = { -2.0, -6.0,  5.0 },        /* Eye location      */
   ProjPlaneCenter = { -1.0, -3.0,  2.5 };        /* Half dist to 0,0,0*/

ColorVector BackgroundColor = { 0.55, 0.44, 0.49 }, /* Background color  */
                 LightColor = { 1.0, 1.0, 1.0 };  /* Light Color       */   

ColorVector Plane_one = { 1.00, 1.00, 0.00 },     /* First plane color */
            Plane_two = { 0.00, 0.00, 1.00 },     /* Second plane color */
            Plane_diffuse = { 0.62, 0.14, 0.10 }; /* Plane Diffuse color */ 

Sphere Sphere_1 = { { -1.0,  1.0,  1.0 },         /* (1) Sphere Center */
                       1.0,                       /*     Radius        */
                    {  0.5,  0.5,  0.5 },         /*     ka            */	
					{  1.0,  0.0,  0.0 },         /*     kd            */
                    {  0.9,  0.9,  0.9 } };       /*     ks            */
Sphere Sphere_2 = { {  1.5, -1.5,  2.0 },         /* (2) Sphere Center */
                       .5,                       /*     Radius        */
                    {  0.23,  0.13,  0.7 },       /*     ka            */
                    {  0.6,  0.7,  0.6 },         /*     kd            */
                    {  0.9,  0.9,  0.4 } };       /*     ks            */


/* Function declaration */
ColorVector RayShade(Object closest_object_hit, RayVector ray,
                     Vector intersection_point, Vector surface_normal,
                     GLint depth);
Vector IntersectSphere(Sphere cur_Sphere, Vector U, RayVector ray);


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   +  Function:   sqr                                           +
   +                                                            +
   +  Purpose:  This function returns the square of a value     +
   +                                                            +
   +  Inputs:  (GLdouble) value                                 +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
GLdouble sqr(GLdouble value)
{
  return fabs( value * value );
}

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   +  Function:   MultiplyColor                                 +
   +                                                            +
   +  Purpose:  This function muliplies 2 color vectors			+                                                            
   +															+
   +  Inputs:  (ColorVector) first & second                     +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
ColorVector MultiplyColor(ColorVector first, ColorVector second)
{
  ColorVector final;
  
  final.R = first.R * second.R;
  final.G = first.G * second.G;
  final.B = first.B * second.B;

  return final;
}

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   +  Function:   AddColor                                      +
   +                                                            +
   +  Purpose:  This function returns the sum of two color vect.+
   +                                                            +
   +  Inputs:  (ColorVector) first & second                     +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
ColorVector AddColor(ColorVector first, ColorVector second)
{
  ColorVector final;
  
  final.R = first.R + second.R;
  final.G = first.G + second.G;
  final.B = first.B + second.B;

  return final;
}



/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   +  Function:   Add                                           +
   +                                                            +
   +  Purpose:  This function returns the sum of two vectors.   +
   +                                                            +
   +  Inputs:  (Vector) first & second                          +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
Vector Add(Vector first, Vector second)
{
  Vector final;
  
  final.x = first.x + second.x;
  final.y = first.y + second.y;
  final.z = first.z + second.z;

  return final;
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:    Subtract                                      +
   +                                                            +
   + Purpose:  This function returns the difference of two      +
   +            vectors... second - first                       +
   +                                                            +
   + Inputs:   (Vector) first & second                          +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */    
Vector Subtract(Vector first, Vector second)
{
  Vector final;

  final.x = second.x - first.x;
  final.y = second.y - first.y;
  final.z = second.z - first.z;

  return final;
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:     Negate                                       +
   +                                                            +
   + Purpose:  This function return the negative of a vector.   +
   +                                                            +
   + Inputs:   (Vector) first                                   +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */    
Vector Negate(Vector first)
{
  Vector final;

  final.x = -1.0*first.x;
  final.y = -1.0*first.y;
  final.z = -1.0*first.z;

  return final;  
}

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:     Length                                       +
   +                                                            +
   + Purpose:  This function return the length of a vector.     +
   +                                                            +
   + Inputs:   (Vector) first                                   +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
GLdouble Length(Vector first)
{
  return sqrt(first.x*first.x + first.y*first.y + first.z*first.z);
}

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:     Scale                                        +
   +                                                            +
   + Purpose:  This function returns a scaled vector.           +
   +                                                            +
   + Inputs:   (Vector) first, (double) factor                  +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
Vector Scale(Vector first, GLdouble factor)
{
  Vector final;

  final.x = first.x * factor;
  final.y = first.y * factor;
  final.z = first.z * factor;

  return final;
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:     ScaleColor                                   +
   +                                                            +
   + Purpose:  This function returns a scaled color vector.     +
   +                                                            +
   + Inputs:   (ColorVector) first, (double) factor             +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
ColorVector ScaleColor(ColorVector first, GLdouble factor)
{
  ColorVector final;

  final.R = first.R * factor;
  final.G = first.G * factor;
  final.B = first.B * factor;

  return final;
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:     Normalize                                    +
   +                                                            + 
   + Purpose:  This function normalizes the input vector.       +
   +                                                            +
   + Inputs:    (Vector) first                                  +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
Vector Normalize(Vector first)                           
{
  Vector final;
  GLdouble length;

  length = Length(first);

  if ( fabs(length) <= TOLERANCE )
    return first;
  else
  {
    final.x = first.x / length;
    final.y = first.y / length;
    final.z = first.z / length;

    return final;
  } 
}

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:     Dot                                          + 
   +                                                            +
   + Purpose:  This function return the dot product of two vect.+
   +                                                            +
   + Inputs:  (Vector) first, (Vector) second                   +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
GLdouble Dot(Vector first, Vector second)
{
  return (first.x*second.x + first.y*second.y + first.z*second.z);  
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:   Cross                                          +
   +                                                            +
   + Purpose:  This function returns cross product of two vect. +
   +                                                            +
   + Inputs:  (Vector) first									+
   +		  (Vector) second									+
   +                                                            + 
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
Vector Cross(Vector first, Vector second) 
{
  Vector final;

  final.x = first.y*second.z - first.z*second.y;
  final.y = first.z*second.x - first.x*second.z;
  final.z = first.x*second.y - first.y*second.x;

  return final;
}



/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:   IntersectPlane                                 +
   +                                                            +
   + Purpose:  This function determines if the base plane is    +
   +            intersected.                                    +
   +                                                            +
   + Inputs:   (Vector) U										+
   +           (RayVector) ray									+
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
Vector IntersectPlane(Vector U, RayVector ray)
{
  /* If Plane is NOT intersected then the returned vector's 
     Z component will be equal to -1 signifying a non intersection
     or an intersection outside of the given planes boundaries,
     otherwise the point of intersection will be returned. */
  
  GLdouble s = 0.0, 
           X = 0.0,       /* Plane intersection point...x coordinate */ 
           Y = 0.0;       /*    "       "        "      y      "     */
  Vector inter_pt = { -1.0, -1.0, -1.0 }; /* Intersection point */

  /* s = -zo / zd */
  s = -ray.orig.z / U.z;

  if ( s > 0 ) /* Intersected plane...find intersection point */
  {
    /* Find point of intersection in xy plane of grid. */
    inter_pt = Add(ray.orig, Scale(U,s));
    inter_pt.z = 0.0;  /* set to zero since using Add won't set it to zero */

    /* Must now test to make sure this intersection point actually lines
       within the limits of the grid plane and not simply intersect the
       "infinite" plane. */
    /* The plane is:
                ^
       (-4,4)   |    (4,4)
          +-----|-----+
          |     |     |
          |     |     |
          +-----+--------->
          |     |     |      
          |     |     | 
          +-----+-----+
       (-4,-4)       (4,-4)
                                                               */
    if ( !((inter_pt.x >= -4.0) && (inter_pt.x <= 4.0) &&
           (inter_pt.y >= -4.0) && (inter_pt.y <= 4.0)) )
      inter_pt.z = 1.0;
  } /* else No intersection ... return -1 in z component of vector */

  return inter_pt;
}



/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:   PlaneColor                                     +
   +                                                            +
   + Purpose:  This function return the plane color when a ray  +
   +           intersects it.					+
   +                                                            +
   + Inputs:  (Vector) inter_pt					+
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
ColorVector PlaneColor(Vector inter_pt)
{
  GLint i = 0, j = 0, square = 0;
  GLdouble Xmin = 0.0, Ymin = 0.0;
  ColorVector color = { 0.0, 0.0, 0.0 };

  RayVector LightToInt;
  Vector inter2, inter3;

  color = BackgroundColor;

  /* Determine in which square of plane intersection point is */

  /* Loop through the squares... */
  for ( j=0 ; j<4 ; j++ )
    for ( i=0 ; i<4 ; i++ )
    {
      Ymin = j*2.0 - 4.0;
      Xmin = i*2.0 - 4.0;

      square = i+j;

      if ( (inter_pt.x >= Xmin) && (inter_pt.x <= Xmin+2.0) &&
           (inter_pt.y >= Ymin) && (inter_pt.y <= Ymin+2.0) )
	  {
        /* Check if point is in shadow */
        LightToInt.u = Normalize(Subtract(LightCenter,inter_pt));
        LightToInt.orig = LightCenter;
		  
        inter2 = IntersectSphere(Sphere_1, LightToInt.u, LightToInt);
        inter3 = IntersectSphere(Sphere_2, LightToInt.u, LightToInt);   
    
        /* if intersected a sphere then make color dark */
        if ( (inter2.z >= 0.0) || (inter3.z >= 0.0) )
		{ 
          if ( square%2 == 0 ) /* Even */
            color = Plane_one;
          else 
            color = Plane_two; 

		   /* Subtract colors to make a shadow */
		   color.R -= 0.3;
		   color.G -= 0.3;
		   color.B -= 0.3;

		   return color;
		}
		else
		{
  		  if ( square%2 == 0 ) /* Even */
            return Plane_one;
          else 
            return Plane_two;
		}
	  }
	}

  return color;
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:  IntersectSphere                                 +
   +                                                            +
   + Purpose:  This function determines if a sphere is          +
   +            intersected...returns intersection point.       +
   +                                                            +
   + Inputs: (Sphere) cur_Sphere				+
   +         (Vector) U						+
   +         (RayVector) ray					+
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
Vector IntersectSphere(Sphere cur_Sphere, Vector U, RayVector ray)
{ 
  GLdouble s = 0.0, 
           descr = 0.0;  
  Vector  inter_pt = { -1.0, -1.0, -1.0 }; /* Intersection point */
  Vector  deltaP;
  
  deltaP = Subtract(ray.orig,cur_Sphere.center);
  descr = sqr(Dot(U,deltaP)) - sqr(Length(deltaP))+sqr(cur_Sphere.radius);

  if (descr <0.0)
	  return inter_pt;
  else
  {
     s = Dot(U,deltaP) - descr;

	 if ( s < TOLERANCE )
	   s = Dot(U,deltaP) + descr;

	 if ( s < TOLERANCE )
       return inter_pt;

	 inter_pt = Add(ray.orig, Scale(U,s));
  }

  return inter_pt;  
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            + 
   + Function:  SphereNormal                                    +
   +                                                            +
   + Purpose:  This function returns the sphere normal: P-Pc    +
   +                                                            +
   + Inputs: (Vector) P  - intersection point on sphere         +
   +                  Pc - sphere center                        +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ 
Vector SphereNormal(Vector P, Vector Pc)
{
  return Normalize(Subtract(Pc,P));
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:  RayTrace                                        +
   +                                                            +
   + Purpose:  This function traces a ray upto 5 recursions     +
   +                                                            +
   + Inputs: (RayVector) ray - ray from eye                     +
   +         (GLint) depth   - depth of recursion               +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ 
ColorVector RayTrace(RayVector ray , GLint depth)
{
  GLdouble closest_dist = LARGE_NUM, 
           dist = LARGE_NUM*2.0;
  Vector inter_pt, 
         final_inter_pt,
         surface_normal;
  Object hit_object = none;

  /* Determine the closest_object_hit by ray */

  /* check PLANE */  
  inter_pt = IntersectPlane(ray.u, ray);
  if ( inter_pt.z >= 0.0 )
  {
    closest_dist = Length( Subtract(EyeCenter,inter_pt) );
    final_inter_pt = inter_pt;
    hit_object = plane;
  }

  /* check SPHERE #1 */
  inter_pt = IntersectSphere(Sphere_1, ray.u, ray);
  if ( inter_pt.z >= 0.0 )
  {
    dist = Length( Subtract(EyeCenter,inter_pt) );
 
    if ( dist < closest_dist)
    {
      closest_dist = dist;
      final_inter_pt = inter_pt;
      hit_object = sphere1;
    }
  } 

  /* check SPHERE #2 */
  inter_pt = IntersectSphere(Sphere_2, ray.u, ray);  
  if ( inter_pt.z >= 0.0 )
  {
    dist = Length( Subtract(EyeCenter,inter_pt) );
 
    if ( dist < closest_dist)
    {
      closest_dist = dist;
      final_inter_pt = inter_pt;
      hit_object = sphere2;
    }
  }


  /* if (hit_object) ... */
  if ( hit_object )
  {
    /* Compute surface_normal at intersection_point */
    switch( hit_object )
    {
      case plane:
        surface_normal = PlaneNormal; 
	break;
      case sphere1:
        surface_normal = SphereNormal(final_inter_pt, Sphere_1.center);
        break;
      case sphere2:   
        surface_normal = SphereNormal(final_inter_pt, Sphere_2.center);
	break;
      case none:
      default:
        printf("\nError: ( RayTrace - 1 ) No object hit.\n");
        break;
   }

    /* Return RayShade */
    return RayShade(hit_object, ray, final_inter_pt, surface_normal, depth);
  }
  else 
    return BackgroundColor;
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:  AmbientColor                                    +
   +                                                            +
   + Purpose:  This function return the ambient color component +
   +                                                            + 
   + Inputs:  (Vector) inter_pt - intersection point            +
   +          (Object) object_hit								+
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
ColorVector AmbientColor(Vector inter_pt, Object object_hit)
{
  ColorVector color;

  if ( object_hit == plane )
    color = PlaneColor(inter_pt);
  else if ( object_hit == sphere1 )
    color = MultiplyColor(Sphere_1.ka, LightColor);
  else if ( object_hit == sphere2 )
    color = MultiplyColor(Sphere_2.ka, LightColor);
  else
    printf("\n Error: (AmbientColor - 1) No object hit.\n");

  return color;
}

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:  DiffuseColor                                    +
   +                                                            +
   + Purpose:  This function returns the diffuse color component+
   +                                                            + 
   + Inputs: (Object) object_hit - object hit					+
   +         (Vector) Normal - surface normal					+
   +		 (Vector) ToLight									+
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
ColorVector DiffuseColor(Object object_hit, Vector Normal, 
						 Vector ToLight)
{
  ColorVector color;

  if ( object_hit == plane )
    color = MultiplyColor(Plane_diffuse, LightColor);
  else if ( object_hit == sphere1 )
    color = MultiplyColor(Sphere_1.kd,   LightColor);
  else if ( object_hit == sphere2 )
    color = MultiplyColor(Sphere_2.kd,   LightColor);
  else
    printf("\n Error: (DiffuseColor - 1) No object hit.\n");

  color = ScaleColor(color, pow(Dot(Normal,ToLight),5));  

  return color;
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:  SpecularColor                                   +
   +                                                            +
   + Purpose:  This function returns the specular color comp.   +
   +                                                            + 
   + Inputs: (Object) object_hit - object hit 			+
   +         (Vector) Normal - surface normal			+
   +		 (Vector) ToLight				+
   +		 (Vector) ToEye					+
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
ColorVector SpecularColor(Object object_hit, Vector Normal, 
						  Vector ToLight, Vector ToEye)
{
  ColorVector color;   /* Color Vector */
  Vector H, VplusL;    /* Vector used to calculate specular color */

  if ( object_hit == plane )
    printf("\n Error: (SpecularColor - 1) Plane has no Specular properties.\n");
  else if ( object_hit == sphere1 )
    color = MultiplyColor(Sphere_1.ks,   LightColor);
  else if ( object_hit == sphere2 )
    color = MultiplyColor(Sphere_2.ks,   LightColor);
  else
    printf("\n Error: (SpecularColor - 2) No object hit.\n");

  VplusL = Add(ToEye,ToLight);
  H = Scale(VplusL,1.0/Length(VplusL));

  color = ScaleColor(color, pow(Dot(Normal,H),100));  

  return color;
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   + Function:  RayShade                                        +
   +                                                            +
   + Purpose:  This function perform the pixel color            +
   +           calculations based on the ambient, diffuse and   +
   +           specular components.                             +
   +                                                            + 
   + Inputs: (Object) closest_object_hit                        +
   +         (RayVector) ray                                    + 
   +         (Vector) intersection_point                        +
   +         (Vector) surface_normal                            +
   +         (GLint) depth                                      +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
ColorVector RayShade(Object closest_object_hit, RayVector ray,
                     Vector intersection_point, Vector surface_normal,
                     GLint depth)
{
  /* local variables */
  ColorVector color = { 0.0, 0.0, 0.0 },     /* Color of the ray */
              rColor;  /* Reflected ray color */
  RayVector rRay, sRay;  /* Reflected and shadow rays */
  Vector inter2, inter3; /* Temporary vectors. */
  RayVector LightToInt;  /* Ray from Light to Intersection point. */

  /* Add ambient_color component */  
  color = AmbientColor(intersection_point, closest_object_hit);   
  
  /* sRay = ray to light from intersection_point */
  sRay.u = Normalize(Subtract(LightCenter,intersection_point));
  sRay.orig = intersection_point;

  LightToInt.u = Negate(sRay.u);
  LightToInt.orig = LightCenter;

  if ( Dot(surface_normal,sRay.u) > 0)
  {
    /* Check to see if closest_object_hit has no objects between it
       and the light; if there are no such objects, add the diffuse and
       specular components to color. */
    inter2 = IntersectSphere(Sphere_1, LightToInt.u, LightToInt);
    inter3 = IntersectSphere(Sphere_2, LightToInt.u, LightToInt);   
    
    /* If nothing is intersected then add specular and diffuse components. */
    if ( (inter2.z < 0.0) && (inter3.z < 0.0) )
      color = AddColor( AddColor(color,DiffuseColor(closest_object_hit,
	                surface_normal,sRay.u)),
	             SpecularColor(closest_object_hit,
				 surface_normal, sRay.u, Negate(ray.u))); 

  }

  if (depth < MAXDEPTH)
  {
    /* if (object is reflective) i.e. either sphere 1 or sphere 2 */
    if ( (closest_object_hit == sphere1) || (closest_object_hit == sphere2) )
    { 
      /* ray in reflection direction from intersection_point */
      /* R = U - (2*u . N)N */
      rRay.u = Subtract(Scale(surface_normal,
                        Dot(Scale(ray.u,2.0),surface_normal)), ray.u);
      rRay.u = Normalize(rRay.u);
      rRay.orig = intersection_point;

      rColor = RayTrace(rRay, depth+1);
     
      /* scale rColor by reflection coefficient and add to color */
      rColor = ScaleColor(rColor,ReflectionCoefficient);

      color = AddColor(color,rColor);
    }
  }

  return color;
}



/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +     
   + Function:  FindPerpendicular                               +
   +                                                            +
   + Purpose:  This function returns a vector perpendicular to  +
   +            the one given it.                               +
   +                                                            +
   + Inputs: (Vector) A                                         +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ 
Vector FindPerpendicular(Vector A)
{
  Vector B;

  /* First in-plane vector is one who's DOT product with the NORMAL
  vector is zero or CROSS product is a unit vector ...
  ... This can be done by making one component of the vector
  zero, another any number and then finding the third component
  by the following equation:
      DOT Product:  A1B1 + A2B2 + A3B3 = 0 if perpendicular
  so if we know A=(A1 A2 A3) and we set B2=( ? 1 0) then we can
  find B1 by:
               B1 = ( -A2B2 - A3B3 ) / A1 = -A2/A1
                                                                */  
  B.y = 1.0;
  B.z = 0.0;

  B.x = -A.y / A.x;

  return Normalize(B);
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   +  Function:    reshape                                      +
   +                                                            +
   +  Purpose: This function allows the screen to be resized    +
   +            but the line draw still appears in its original +
   +            position and length.                            +
   +                                                            +
   +  Inputs: w and h - width and height of window              +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
void reshape (int w, int h)
{
  glViewport (0, 0, (GLsizei) w, (GLsizei) h);
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  gluOrtho2D (0.0, (GLdouble) w,(GLdouble) h, 0.0);
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   +  Function:    SetCameraPosition                            +
   +                                                            +
   +  Purpose: This function sets the postion of the camera     +
   +                                                            +
   +  Inputs: none                                              +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
void SetCameraPosition(void)
{
  /* Set the viewing transformation */
  glMatrixMode(GL_PROJECTION); 
  glLoadIdentity();
  gluOrtho2D(0.0,(GLdouble)WinWidth+1.0,(GLdouble)WinHeight+1.0,0.0);
  glMatrixMode(GL_MODELVIEW);
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   +  Function:    init                                         +
   +                                                            +
   +  Purpose: This function clears the colors and initializes  +
   +            the shade model.                                +
   +                                                            +
   +  Inputs: none                                              +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */   
void init(void)
{
  glClearColor(0.0, 0.0, 0.0, 0.0);
  glShadeModel(GL_FLAT);
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   +  Function:    display                                      +
   +                                                            +
   +  Purpose: To display each pixel with appropriate color     +
   +            based on the ray traced from that point.        +
   +                                                            +
   +  Inputs: none                                              +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
void display(void)
{
  /* Initialize variables */
  GLint i, j;

  GLdouble XhalfDist, YhalfDist, X_loc, Y_loc;

  Vector VecToCenterScreen,
         PerpendToPlane,
         Axis2nd,
         Axis3rd,
         InPlaneVector,
         u_vector;  

  ColorVector pixel_color;
 
  RayVector ray;

  /* Set background color */
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(BackgroundColor.R, BackgroundColor.G, BackgroundColor.B);


  /* Select the center of projection (eyepoint) and window on view plane */
  /* The center of the window on view plane will be along line to (0,0,0) */
  /* I normalize this vector maybe? */
  VecToCenterScreen = Normalize(Subtract(EyeCenter,ProjPlaneCenter));

  /* Vector from plane towards eye */
  PerpendToPlane = Negate(VecToCenterScreen);  

  /* Find 2nd axis which is perpendicular to normal vector...returns normalized */
  Axis2nd = FindPerpendicular(PerpendToPlane);
  
  /* Using CROSS product, find third axis */
  Axis3rd = Cross(PerpendToPlane,Axis2nd);


  /* Find the halfway distance of the window in both x & y directions */
  XhalfDist = WinWidth/2.0;
  YhalfDist = WinHeight/2.0;

  for ( i=0 ; i<WinHeight ; i++  )   /* for each scan line in image */ 
    for ( j=0 ; j<WinWidth ; j++  )   /* for each pixel in scan line */
    {
      /* Determine the ray from the center of projection through the pixel */
      /* The ray is found assuming the map window is a unit square
         with center at (0,0) and in world coordinates at ProjPlaneCenter */
      /* Calculate in-viewing-plane vector to each pixel ... This gives
         the location of the point on the unit sphere. */
      X_loc = ( j - XhalfDist ) / WinWidth;
      Y_loc = ( i - YhalfDist ) / WinHeight;
                
      /* Transform from plane coordinates to world coordinates */
      InPlaneVector = Add( Scale(Axis2nd, X_loc),
                           Scale(Axis3rd, Y_loc) );
 
      /* Add the vectors to get the u vector */
      u_vector = Add(VecToCenterScreen, InPlaneVector);

      /* Finally, normalize the u vector */
      ray.u = Normalize(u_vector);
      ray.orig = EyeCenter;

      /* Begin Ray Tracing of this particular ray. */
      pixel_color = RayTrace(ray,1);

      /* draw pixel_color at current pixel position */ 
      glColor3f(pixel_color.R, pixel_color.G, pixel_color.B);
      glBegin(GL_POINTS);
        glVertex2i(j,i);
      glEnd();
    }

  glFlush(); 
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   +  Function:    KeyPress                                     +
   +                                                            +
   +  Purpose: This function will check to see if the key       +
   +            pressed is a q (or Q). If so, then the program  +
   +            will terminate gracefully.                      +
   +                                                            +
   +  Inputs: Key (unsigned char) - Key pressed.                +
   +          X (int) - x location of mouse pointer.            +
   +          Y (int) - y location of mouse pointer.            +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
void KeyPress(unsigned char Key, int X, int Y)
{
  if (toupper(Key) == 'Q')
    exit(0);
}
 

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
   +                                                            +
   +  Function:    main                                         +
   +                                                            +
   +  Purpose: This is the main function of mp6. Most of the    +
   +            initialization of the display is done here and  +
   +            functions for events are invoked here.          +
   +                                                            +
   +  Inputs: none.                                             +
   +                                                            +
   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
int main(int argc, char **argv)
{
  /* Display quit procedure. */
  printf("CS318 - MP6 - Ray Tracer\n");
  printf("INPUTS:\n");
  printf("'q' or 'Q'    - Quit Program\n");

  /* Initialize window. */
  glutInit(&argc, argv);
  glutInitWindowSize(WinWidth, WinWidth);
  glutInitWindowPosition(100, 100);
  glutCreateWindow("MP6: Ray Tracer");
  init();

  /* Define camera position. */
  SetCameraPosition();

  /* Call display function and enter main loop. */
  glutDisplayFunc(display);
  glutKeyboardFunc(KeyPress);
  glutReshapeFunc(reshape);
  glutMainLoop();

  return 0;
}

