
/*
    This header file defines a set of classes that can be used
    as a simple object-oriented, hierarchical 3D graphics system
    built on top of OpenGL.  The main classes include:
    Transform (representing a geometric transformation such as
    rotation or translation), View (representing a view of a
    3D world), and Object (representing anything that can be
    part of a 3D world such as lights, basic objects, and
    hierarchical models).
       The most important sub-class of Object is Model.  A Model
    is an object that can contain other objects, including other models.
    Transformations that are applied to a model apply to all the
    objects in the model, in addition to any transformations applied
    to the objects themselves.  This is the basic requirement for
    hierarchical graphics.
       Another important subclass of Object is Light.  A Light
    object can be transformed just like any other object, and
    it can be part of a Model.
       Basic graphical objects such as cubes, spheres, cones, and
    squares are represented by the BasicObject class.  Other shapes
    can be represented as IndexedFaceSet objects, which can 
    be arbitrary polygonal meshes.

*/


#include <string>

/*
 *  An enumerated type that for use with the BasicObject class.
 *  Each element of this enum represents one type of BasicObject.
 */
enum BasicObjectType {
   LINE, SQUARE, CIRCLE, 
   CUBE, SPHERE, CONE, CYLINDER,
   TETRAHEDRON, OCTAHEDRON, DODECAHEDRON, ICOSAHEDRON,
   TORUS, TEAPOT
};

/*
 *  An enumerated type representing the two types of projection
 *  that are possible in the View class.
 */
enum Projection {
   ORTHOGRAPHIC, PERSPECTIVE
};

/*
 *  Two enumerated types used only for specifying options
 *  in class IndexedFaceSet.
 */
enum IFSNormalFlag {
   NO_NORMALS, NORMAL_BY_FACE, NORMAL_BY_VERTEX
}; 
enum IFSColorFlag {
   NO_COLORS, COLOR_BY_FACE, COLOR_BY_VERTEX
}; 


class Object;     // (A 3D graphics object.)
class Transform;  // (A 3D geometric transformation.)


/*
 *  The View class represents a view of a 3D world.  The
 *  world contains one Object, which is almost certain to
 *  be of type Model so that it can include lights and other
 *  subobjects.  A View acts like a camera.  It contains
 *  viewing parameters such as eye position and projection type.
 */
class View {
      Object *object;
      Projection projType;
      bool keepAspect;
      bool standardDefaults;
      float *background;
      float xmin, xmax, ymin, ymax, zmin, zmax;
      float viewXmin,viewXmax, viewYmin, viewYmax;
      float eyex, eyey, eyez;
      float refx, refy, refz;
      float upx, upy, upz;
   public:

      View(Object *world = 0, Projection projectionType = PERSPECTIVE);
         // Create a view that will use the specified projection type
         // for viewing the object (which should ordinarily be a Model).
         // If the world is 0, this View will not show any content.

      virtual ~View();
         // Note:  Destructor does NOT delete the world object.

      void render();
         // Call this in an OpenGL drawing context (the display function)
         // to draw this View's view of the world.

      void setWorld(Object *world);
         // Set the world that is displayed by this view.

      void setProjectionType(Projection proj);
         // Set projection, either ORTHOGRAPHIC or PERSPECTIVE.

      Projection getProjectionType();
         // Returns the current projection type used by this View.
         
      void setKeepAspectRatio(bool keep);
         // If this is set to false, then the x or y limits will
         // be adjusted when the viewing window is not square to
         // preserve the aspect ratio of objects in the world.
         // The default value is true.

      bool getKeepAspectRatio();
         // Returns the value set by the previous function.

      void setUseStandardDefaults(bool useStandardDefaults);
         // By default, the View sets up certain reasonable defaults,
         // such as turning on GL_DEPTH_TEST and GL_NORMALIZE, before
         // rendering the world.  If you set this to false, this is
         // not done and you take responsibility for setting up
         // such defaults.

      bool getUseStandardDefaults();
         // Returns the value set by the previous function,.
         
      void setViewCenter(float x, float y, float z);
         // The camera is pointed at this point.  Default: (0,0,0).
         
      void setViewEye(float x, float y, float z);
         // Sets the camera/eye location.  Default: (0,0,20).
         
      void setViewUp(float dx, float dy, float dz);
         // Sets the upward vector for the camera.  Default: (0,1,0).
         
      void setViewVolume(float left, float right, float bottom,
                         float top, float far, float near);
         // Sets the volume of space that is projected onto the image.
         // This function specifies the six clipping planes of that region.
         // These parameters are DIFFERENT FROM those in glOrtho or
         // glFrustum.  The "far" and "near" values are given with
         // repect to the ViewCenter rather than as distances from
         // the eye.  They serve as minimum and maximum z-values for 
         // the view.  For a perspective transformation, left, right, 
         // bottom, and top are specified at the distance of the ViewCenter 
         // from the Eye, not at the near clipping distance.  This should be 
         // more natural than the OpenGL parameters:  Just specify minimum
         // and maximum x, y, and z values.  Default: (-5,5,-5,5,-10,10).

      void setNDCViewport(float xstart, float xend, float ystart, float yend);
         // By default, the view will fill the entire OpenGL display window.
         // If you want to use only a part of the window, call this function.
         // This sets the display area for the view in "Normalized Device
         // Coordinates" in which x and y values on the window go from
         // 0 to 1.  The default values (0,1,0,1) represent the whole window.
         // The parameters should satisfy 0 <= xstart < xend <= 1 and
         // 0 <= ystart < yend <= 1.

      void setBackground(float red, float green, float blue);
         // Set a background color for this view.  If you do NOT set
         // a background color, then you are responsible for setting
         // a glClearColor.  Ordinarily, you should just set the ClearColor
         // and only use this background color if you set a NDVViewport
         // that is smaller than the whole window.
};


/*
 *  An "Object" is something that can appear in a 3D world, including
 *  lights, basic objects such as cubes and cones, and "Models" which
 *  are objects that are made up of other objects.  An object can 
 *  have associated colors and geometric transformations,
 */
class Object {
      Transform **transforms;
      bool *autodel;
      int transformCount, transformArraySize;
   protected:
      virtual void renderObject() = 0; // Renders the object itself.
      virtual void renderLights();  // Render any contained lights.
      float *color;
      float *specular;
      float *emissive;
      bool lit;
      bool visible;
   public:

      Object();
      virtual ~Object();
         // (This is an abstract class -- constructor and destructor
         // cannot be called directly.)

      void render();
         // When called in an OpenGL context, this will draw the
         // object.  This method is not virtual.  You should not override
         // it in a subclass.  Override the protected method
         // renderObject().  This method takes care of setting colors 
         // and other properties and calls renderObject() to do the 
         // actual drawing.

      void turnOnLights();
         // Turn on any lights contained in this object.  This is separate
         // from render() because all the lights in a world must be
         // turned on before any of the objects are drawn.  Do not 
         // override this method in a subclass.  Override renderLights().

      virtual bool containsLights();
         // This is used for efficiency it should be defined in a subclass
         // to return true if this object contains any lights.

      Object *addTransform(Transform *transform, bool autodelete = 0);
         // Add a Transform that will be applied to this object
         // when it is drawn.  Transforms are applied in the order
         // in which they were added to the object.  The return value
         // is a pointer to this object, which allows you to string
         // together a series of calls to functions like addTransform
         // and setColor.
         
      virtual Object *setColor(float red, float green, float blue);
         // Set the color of this object.  This color is used as the
         // drawing color for unlit objects and as the ambient and
         // diffuse colors of lit objects.  (It is virtual because
         // setting colors is a little different for lights.)  The
         // parameters should be between 0 and 1 and are clamped to
         // that range.  The return value is a pointer to this object.

      Object *setSpecularColor(float red, float green, float blue);
         // Set the specular material color of the object.  The
         // parameters should be between 0 and 1 and are clamped to
         // that range.  The return value is a pointer to this object.

      Object *setEmissiveColor(float red, float green, float blue);
         // Set the emissive material color of the object.  The
         // parameters should be between 0 and 1 and are clamped to
         // that range.  The return value is a pointer to this object.

      Object *setIsLit(bool isLit);
         // If this is set to false, then lighting is turned off while
         // this object is being rendered.  The default value is true.
         // The return value is a pointer to this object.
         
      Object *setVisible(bool isVisible);
         // If this is set to false, then the object is not drawn at
         // all.  The default value is true.  Setting a Light to be
         // invisible will turn off the light.  Setting a Model to be
         // invisible will make all the objects in it invisible, 
         // regardless of their own visibility settings.
         // The return value is a pointer to this object.

      void getColor(float &red, float &green, float &blue);
      void getSpecularColor(float &red, float &green, float &blue);
      void getEmissiveColor(float &red, float &green, float &blue);
      bool isLit();
      bool getIsVisible();
         // These five functions return information about the
         // settings of variables in this object.  If a color value
         // has not been set for this object, the values of red,
         // green, and blue will all be set to -1.

      int getTransformCount();
         // Get the number of transforms that have been added to this object.

      Transform *getTransform(int index);
         // Get the specified transform, where numbering starts at 0.
         // If the parameter is outside the valid range, NULL is returned.

      void deleteTransform(int index);
         // Removes the specified transform.  If index is outside the
         // valid range, this is ignored.
         
};


/*
 *  A "Model" is a graphics Object that can contain other objects.
 *  Transformations applied to a Model also apply to the objects that
 *  it contains, in addition to their own transformations.  Since
 *  a model can contain objects that are themselves models, it is
 *  possible to do hierarichical graphics.
 */
class Model: public Object {
      Object **objects;
      bool *autodel;
      int objectCount, objectArraySize;
   protected:
      virtual void renderObject();
      virtual void renderLights();
   public:

      Model();
         // Create an initially empty model.
         
      Model(Object *object, Transform *transform = 0);
         // Creates a model containing the single specified
         // object.  If transform is not 0, then the 
         // transform is added to the model object.  This
         // is meant as an easy way to make a transformed
         // object.
         
      virtual ~Model();
         // Destroy the model.  Objects in the model are not
         // automatically destroyed, unless they were added 
         // with the autodelete option.

      virtual bool containsLights();
         // Returns true if this model contains a Light
         // (or if a submodel contains a Light).

      Model *addObject(Object *object, bool autodelete = 0);
         // Adds the specified object to the model.  If the
         // autodelete parameter is true, then the object will
         // be deleted when the model is destructed.  (You would
         // only do this if the object was allocated dynamically
         // and is not shared.  In that case, having it deleted
         // automatically is a convenience, since you don't have
         // to keep another pointer around to delete it manually.)

      int getObjectCount();
         // Returns the number of objects that have been added
         // to this model.

      Object *getObject(int index);
         // Returns one of the objects in the model, where objects
         // are numbered starting from 0.  If index is outside the
         // valid range, then NULL is returned.

      void deleteObject(int index);
         // Removes the specified object.  If index is not in the
         // range 0 to getObjectCount()-1, this is ignored.
};


/*
 *  A BasicObject is one of the 13 basic object types defined in
 *  the enumerated type BasicObjectType given above.  A basic
 *  object can be either solid or wireframe.  For a LINE, it
 *  doesn't make any difference.  A "solid" CIRCLE is a disk.
 */
class BasicObject: public Object {
      BasicObjectType type;
      bool wireframe;
   protected:
      virtual void renderObject();
   public:
      BasicObject(BasicObjectType type, bool isWireframe = 0);
          // Creates a basic object of the specified type.
          // The second parameter specifies whether the object
          // is wireframe or solid.

      BasicObjectType getType();
         // Returns the type of object.
          
      void setType(BasicObjectType type);
         // Sets the object type (so you can turn a SQUARE 
         // into a TEAPOT for example).

      BasicObject *setIsWireframe(bool isWireframe);
          // Sets whether the object is wireframe or solid.

      bool isWireframe();
          // Tells whether this is wireframe or solid.
};


/*
 *  If you have an OpenGL display list, this class allows you to
 *  wrap it in a graphics Object so that you can add it to Views
 *  and Models.  You should not set up lights in the display list
 *  if you want this to work well with the rest of the system.
 *  Note that a display list ID of 0 means that there is no 
 *  display list.  If ID is 0, nothing is drawn.  Other than
 *  that, you are responsible for making sure that the display
 *  list is valid.
 */
class DisplayListObject: public Object {
   protected:
      int id;
      virtual void renderObject();
   public:
      DisplayListObject(int id);
      int getID();
};


/*
 *  An IndexedFaceSet represents a general polygonal surface in which
 *  the coordinates of each vertex are given explicitely.  The data
 *  for the IFS can be taken from a file or from arrays.
 *  WARNING:  This class creates a display list -- which is something
 *  that can only be done in an already initialized OpenGL context
 *  (after glutInit() has been called).
 */
class IndexedFaceSet : public DisplayListObject {
   public:
      IndexedFaceSet(const std::string fileName);
         // This constructor just calls setData(fileName).  If the data in
         // the file is not valid, the display list ID will be 0.
         
      IndexedFaceSet(float vertices[][3] = 0, int vertexCount = 0,
                     int faceData[] = 0, int faceDataCount = 0,
                     IFSNormalFlag normalsBy = NO_NORMALS, float normals[][3] = 0,
                     IFSColorFlag colorsBy = NO_COLORS, float colors[][3] = 0);
         // This constructor just calls setData with the same parameters.

      bool setData(const std::string fileName);
         // The parameter must be the name of a file that contains data for
         // an IFS in an exact format.  The data is just a long sequence of
         // numbers.  The first four numbers are integers specifying:
         //     1. The number of vertices.      
         //     2. The number of integers in the face data.
         //     3. The type of normal data:  0 = no normal data, 1 = a
         //            normal vector for each face, 2 = a normal vector
         //            for each vertex.
         //     4. The type of color data:  0 = no color data, 1 = a color
         //            for each face, 2 = a color for each vertex.
         // This is followed by three real numbers for each vertex, then
         // by the face data, then by the normal data if any (three
         // real numbers for each normal), then by the color data, if any
         // (three real numbers in the range 0.0 to 1.0 for each color).
         // The face data is just a list of integers.  These integers are
         // indexes into the vertex array.  Each face on the IFS is represented
         // by the list of indices for the vertices of that face (in counter
         // clockwise order as seen from the front) followed by a -1 to mark
         // the end of the list.  Note that the face data count is the total
         // number of indices, including all the vertex indices and the -1's.
         // The actual number of faces is inferred.  The vertices are 
         // numbered starting from zero.  The return value is false if
         // an error occurs while reading data from the file (but not
         // every possible error is checked!).

      bool setData(float vertices[][3] = 0, int vertexCount = 0,
                   int faceData[] = 0, int faceDataCount = 0,
                   IFSNormalFlag normalsBy = NO_NORMALS, float normals[][3] = 0,
                   IFSColorFlag colorsBy = NO_COLORS, float colors[][3] = 0);
         // The parameters represent the same data that would be found
         // in the file.  If vertices, vertexCount, faceData, or faceDataCount
         // is zero, then no IFS is created, the return value is false and
         // the display list ID is set to 0.

      virtual ~IndexedFaceSet();
         // The destructor deletes the display list that stores the IFS.
};


/*
 *  A Light object represents a light.  You can only have eight lights in
 *  total in a program.  Lights can be either directional or positional, and
 *  positional lights can be spotlights and can have attenuation.  Note
 *  that lights can be transformed just like other objects.
 */
class Light: public Object {
      int id;
      float attenuation;
      float position[4];
      float colorvec[4];
      bool isSpot;
      float spotDirection[3];
      float spotCutoff;
      float spotExponent;
   protected:
      virtual void renderObject();
      virtual void renderLights();

   public:
      Light(float x, float y, float z, bool isPositional = 1);
          // If the last parameter is true, creates a positional
          // light at the point (x,y,z).  If false, this creates
          // a directional light from the direction of (x,y,z).

      ~Light();
          // Destroys the light (making it possible for another 
          // Light object to take its place within the limit of
          // eight lights).

      virtual bool containsLights();
          // Returns true.

      virtual Object *setColor(float red, float green, float blue);
          // Sets the intensity of the light.  This is used for
          // both the diffuse and specular intensity of the light, and
          // a small fraction of it is used for the ambient intensity.
          // The parameter values can be greater than 1.  Note that
          // and emissive or specular color set for this object is
          // ignored!

      Light *setPosition(float x, float y, float z, bool isPositional = 1);
          // Sets the position/direction of the light and whether it is
          // a positional or directional light.

      Light *setIsSpot(bool isSpot);
      Light *setSpotDirection(float dx, float dy, float dz);
      Light *setSpotCutoff(float spotCutoff);
      Light *setSpotExponent(float spotExponent);
          // These methods set OpenGL spotlight properties of the light.
          // These values only affect positional lights.

      Light *setAttenuation(float attenuation);
          // Sets an attenuation factor for this light.  In OpenGL terms,
          // this is the linear attenuation and the quadratic attenuation
          // is zero.  The default value of the attenuation is 0.05.
          // This only affects positional lights.

      float getAttenuation();
         // Returns the attenuation factor of the light.

};


/*
 *  An abstract class representing a geometric transformation
 *  such as rotation, scaling, or translation.
 */
class Transform {
   public:
      virtual void apply() = 0;
         // Issue the OpenGL commands that add this transformation
         // onto the current transformation.
};


/*
 *  Represents a rotation by a specified angle about a specified
 *  line through the origin.
 */
class Rotate: public Transform{
      float theta;
      float dx, dy, dz;
   public:

      Rotate(float angle, float axis_x, float axis_y, float axis_z);
         // Creates a rotation by the specified angle about the
         // line through (0,0,0) and (axis_x,axis_y,axis_z);

      Rotate(float angle);
         // Creates a rotation by the specifed angle about the z-axis.

      void setAngle(float angle);
         // Sets the rotation angle.

      void setAxis(float axis_x, float axis_y, float axis_z);
         // Sets the rotation axis.

      float getAngle();
         // Gets the rotation angle.
         
      float getAxisX();
      float getAxisY();
      float getAxisZ();
         // Get axis data.

      virtual void apply();
         // Calls glRotatef(angle,axis_x,axis_y,axis_z)
};


/*
 *  Represents a 3D translation.
 */
class Translate: public Transform {
      float tx, ty, tz;
   public:

      Translate(float dx, float dy, float dz);
          // Creates a translation by the vector (dx,dy,dz)

      void set(float dx, float dy, float dz);
          // Set the displacement.

      float getDx();
      float getDy();
      float getDz();
          // Get the displacement values.

      virtual void apply();
          // Calls glTranslatef(dx,dy,dz);
};


/*
 *  Represents a 3D scaling transformation.
 */
class Scale: public Transform {
      float sx, sy, sz;
   public:

      Scale(float scale_x, float scale_y, float scale_z);
          // Creates a scaling transformation with the specified
          // scaling factors in the x, y, and z-directions.

      Scale(float scale);
          // Creates a uniform scaling.  Same as Scale(scale,scale,scale).

      void set(float scale_x, float scale_y, float scale_z);
          // Sets the scaling factors.

      void set(float scale);
          // Same as set(scale,scale,scale);

      float getScaleX();
      float getScaleY();
      float getScaleZ();
          // Get the scaling factors.

      virtual void apply();
          // Calls glScalef(scale_x,scale_y,scale_z);
};

