// grtobject.h
// by Glenn G. Chappell
// April 2004
// Part of GRT package

// Defines GrtObjectBase, GrtObjList, GrtObjectSphere

#ifndef FILE_GRTOBJECT_H_INCLUDED
#define FILE_GRTOBJECT_H_INCLUDED

#include "grttypes.h"
#include "grtray.h"
#include "vecpos.h"
using tf::pos;
using tf::vec;
#include <math.h>  // Ah, if only <cmath> always worked ....
//using std::sqrt;
#include <list>
using std::list;

// ************************************************************************
// Forward declarations
// ************************************************************************

class GrtObjList;


// ************************************************************************
// GrtObjectBase declaration
// ************************************************************************

// class GrtObjectBase
// Abstract base class for all objects in GRT
class GrtObjectBase
{

// ***** GrtObjectBase: ctors, dctor, op= *****
public:
   virtual ~GrtObjectBase() {}

// ***** GrtObjectBase: ray-handling functions *****
public:
   virtual GrtHit hittest(const GrtRay & theray) const = 0;
   virtual GrtColor raytrace(const GrtRay & theray,
                             const GrtHit & thehit,
                             const GrtObjList & objlist,
                             const GrtLightList & lightlist,
                             int recursiondepth) const = 0;

};  // End class GrtObjectBase


// ************************************************************************
// GrtObjList declaration
// ************************************************************************

// class GrtObjList
// List of ptrs to objects (all should be of classes derived from GrtObjBase).
// Objects pointed to are managed by the GrtObjList.
// Knows how to do a ray trace. Keeps background color.
class GrtObjList
{

// ***** GrtObjList: ctors, dctor, op= *****
public:
   GrtObjList(GrtColor (* backgnd)(const GrtRay & theray) = 0,
              int therecursiondepthlimit = 100)
      :backgroundfunction_(backgnd),
       recursiondepthlimit_(therecursiondepthlimit)
   {}
   ~GrtObjList()
   {
      std::list<GrtObjectBase *>::iterator ii;
      for (ii=objects_.begin(); ii!=objects_.end(); ++ii)
         delete *ii;
   }

private:  // no copy ctor, copy op=
   GrtObjList(const GrtObjList & other);
   GrtObjList & operator=(const GrtObjList & rhs);

// ***** GrtObjList: data access *****
public:
   void setbackgroundfunction(GrtColor (* backgnd)(const GrtRay & theray))
   { backgroundfunction_ = backgnd; }

   void setrecursiondepthlimit(int limit)
   { recursiondepthlimit_ = limit; }

// ***** GrtObjList: general public functions *****
public:
   // insert
   // Given ptr to object, inserts ptr to object into list.
   // After this, we manage the memory for that object.
   // Assume it was allocated with new.
   void insert (GrtObjectBase * theobjectp)
   { objects_.push_back(theobjectp); }

   GrtColor doraytrace(const GrtRay & theray,
                       const GrtLightList & lightlist,
                       int recursiondepth = 0) const
   {
      if (recursiondepth > recursiondepthlimit_)
         return GrtColor(0., 0., 0.);

      const double fudge = 0.000000001;  // Fudge factor so don't hit start of ray
      const GrtRay fudgeray(theray.start() + theray.dir() * fudge, theray.dir());
         // Fudged ray: starts just a bit beyond the "official" start point,
         //  so we don't get a hit there

      GrtHit besthit;              // closest hit, if any
      besthit.intersects = false;  // No intersection yet
      
      std::list<GrtObjectBase *>::const_iterator ii, saveii;
      for (ii=objects_.begin(); ii!=objects_.end(); ++ii)
      {
         GrtHit newhit = (*ii)->hittest(fudgeray);
         if (newhit.intersects &&  // We intersected
             (!besthit.intersects || (newhit.dist < besthit.dist)))
         {
            besthit = newhit;
            saveii = ii;
         }
      }

      if (besthit.intersects)  // Got an intersection, do ray trace with object
      {
         return (*saveii)->raytrace(fudgeray, besthit, *this, lightlist, recursiondepth);
      }
      else
      {
         if (backgroundfunction_)
            return backgroundfunction_(fudgeray);
         else
            return GrtColor(1.0, 1.0, 1.0);
      }
   }

// ***** GrtObjList: Data members *****
private:
   std::list<GrtObjectBase *> objects_;  // List of ptrs to objects
   GrtColor (* backgroundfunction_)(const GrtRay & theray);
      // Computes color to return if no ray-object intersection
   int recursiondepthlimit_;

};  // End class GrtObjectList


// ************************************************************************
// GrtObjectSphere declaration
// ************************************************************************

// class GrtObjectSphere
// Sphere for ray tracing. Derived from GrtObjectBase
// Only does specular reflection
class GrtObjectSphere : public GrtObjectBase
{

// ***** GrtObjectSphere: ctors, dctor, op= *****
public:
   GrtObjectSphere(const pos & thecenter=pos(),
                   double theradius = 1.,
                   GrtColor themycolor = GrtColor())
      :GrtObjectBase(),
       center_(thecenter),
       radius_(theradius <= 0. ? 1. : theradius),
       mycolor_(themycolor)
   {}
   virtual ~GrtObjectSphere() {}

   // Auto-generated copy ctor, copy op= are fine

// ***** GrtObjectSphere: data access *****
public:
   void setcenter(const pos & thecenter)
   { center_ = thecenter; }
   void setradius(double theradius)
   { radius_ = theradius; }
   void setcolor(const GrtColor & themycolor)
   { mycolor_ = themycolor; }

// ***** GrtObjectSphere: ray-handling functions *****
public:
   // hittest
   // Ray-sphere intersection test.
   // Based on "Intersection of a Ray with a Sphere",
   // by Jeff Hultquist, in _Graphics Gems I_,
   // Andrew S. Glassner, ed., pp 388-389.
   // (This article has an error in the case where the ray
   // begins inside the sphere; I have fixed this here.)
   virtual GrtHit hittest(const GrtRay & theray) const
   {
      GrtHit thehit;  // hit to return

      const vec eo = center_ - theray.start();  // Vector from ray start to my center.
                                                //  "EO" in Graphics Gems article.
      const double v = eo.dot(theray.dir());    // Distance from ray start to
                                                //  midpoint of sphere intersections,
                                                //  if this makes sense.
                                                //  "v" in Graphics Gems article.
      const double disc = radius_*radius_ - (eo.lengthsqr() - v*v);
         // Discriminant
      if (disc < 0.)  // No line-sphere intersection
      {
         thehit.intersects = false;
         return thehit;
      }

      const double d = sqrt(disc);  // Half of distance between sphere
                                    //  intersections, if this makes sense.
                                    //  "d" in Graphics Gems article.

      if (v-d > 0.)                 // Sphere is in front of ray
      {
         thehit.dist = v-d;
      }
      else if (v+d > 0.)            // Ray starts inside sphere
      {
         thehit.dist = v+d;
      }
      else                          // Sphere is behind ray
      {
         thehit.intersects = false;
         return thehit;
      }

      thehit.where = theray.start() + thehit.dist * theray.dir();
      thehit.norm = (thehit.where - center_).normalized();
      thehit.intersects = true;

      return thehit;
   }

   virtual GrtColor raytrace(const GrtRay & theray,
                             const GrtHit & thehit,
                             const GrtObjList & objlist,
                             const GrtLightList & lightlist,
                             int recursiondepth) const
   {
      GrtRay newray = theray.reflect(thehit);
      GrtColor reflectedraycolor = objlist.doraytrace(newray, lightlist, 1+recursiondepth);
      return reflectedraycolor.coordwiseprod(mycolor_);
   }
   
// ***** GrtObjectSphere: Data members *****
private:
   pos center_;            // Center of sphere
   double radius_;         // Radius of sphere; always > 0.
   GrtColor mycolor_;      // Color of sphere

};  // End class GrtObjectSphere


#endif // #ifndef FILE_GRTOBJECT_H_INCLUDED
