
/**********************************************************************
 * GIFPlot 0.0
 * 
 * Dave Beazley
 * 
 * Department of Computer Science        Theoretical Division (T-11)        
 * University of Utah                    Los Alamos National Laboratory     
 * Salt Lake City, Utah 84112            Los Alamos, New Mexico  87545      
 * beazley@cs.utah.edu                   beazley@lanl.gov                   
 *
 * Copyright (c) 1996
 * The Regents of the University of California and the University of Utah
 * All Rights Reserved
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that 
 * (1) The above copyright notice and the following two paragraphs
 * appear in all copies of the source code and (2) redistributions
 * including binaries reproduces these notices in the supporting
 * documentation.   Substantial modifications to this software may be
 * copyrighted by their authors and need not follow the licensing terms
 * described here, provided that the new terms are clearly indicated in
 * all files where they apply.
 * 
 * IN NO EVENT SHALL THE AUTHOR, THE UNIVERSITY OF CALIFORNIA, THE 
 * UNIVERSITY OF UTAH OR DISTRIBUTORS OF THIS SOFTWARE BE LIABLE TO ANY
 * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
 * DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION,
 * EVEN IF THE AUTHORS OR ANY OF THE ABOVE PARTIES HAVE BEEN ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE AUTHOR, THE UNIVERSITY OF CALIFORNIA, AND THE UNIVERSITY OF UTAH
 * SPECIFICALLY DISCLAIM ANY WARRANTIES,INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND 
 * THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE,
 * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 **************************************************************************/

/*************************************************************************
 * plot3d.c
 *
 * Routines for "simple" 3D plots.
 *************************************************************************/

#define PLOT3D
#include "gifplot.h"
#include <math.h>
#include <float.h>

#define ORTHO        1
#define PERSPECTIVE  2
/* ------------------------------------------------------------------------
   Plot3D *new_Plot3D(FrameBuffer *f, double xmin, double ymin, double zmin,
   double xmax, double ymax, double zmax)

   Creates a new 3D plot.  Min and max coordinates are primarily used to
   pick some default parameters.  Returns NULL on failure
   ------------------------------------------------------------------------- */

Plot3D *new_Plot3D(FrameBuffer *f, double xmin, double ymin, double zmin,
		   double xmax, double ymax, double zmax) {

  Plot3D  *p3;
  void Plot3D_maketransform(Plot3D *p3);
  
  /* Check to make sure the framebuffer and min/max parameters are valid */

  if (!f) return (Plot3D *) 0;
  if ((xmin > xmax) || (ymin > ymax) || (zmin > zmax)) return (Plot3D *) 0;

  p3 = (Plot3D *) malloc(sizeof(Plot3D));
  p3->frame = f;
  p3->xmin = xmin;
  p3->ymin = ymin;
  p3->zmin = zmin;
  p3->xmax = xmax;
  p3->ymax = ymax;
  p3->zmax = zmax;

  /* Set view region to the entire size of the framebuffer */

  p3->view_xmin = 0;
  p3->view_ymin = 0;
  p3->view_xmax = f->width;
  p3->view_ymax = f->height;
  p3->width = f->width;
  p3->height = f->height;
  
  /* Calculate a center point based off the min and max coordinates given */

  p3->xcenter = (xmax - xmin)/2.0 + xmin;
  p3->ycenter = (ymax - ymin)/2.0 + ymin;
  p3->zcenter = (zmax - zmin)/2.0 + zmin;

  /* Calculate the aspect ratio of the viewing region */

  p3->aspect = (double) f->width/(double) f->height;

  /* Set default view parameters */
  p3->xshift = 1.0;
  p3->yshift = 1.0;
  p3->zoom   = 0.5;
  p3->fovy   = 40.0;         /* 40 degree field of view */

  /* Create matrices */

  p3->model_mat = new_Matrix();
  p3->view_mat = new_Matrix();
  p3->center_mat = new_Matrix();
  p3->fullmodel_mat = new_Matrix();
  p3->trans_mat = new_Matrix();
  p3->pers_mode = ORTHO;

  FrameBuffer_zresize(p3->frame,p3->width, p3->height);
  Matrix_identity(p3->view_mat);
  Matrix_identity(p3->model_mat);
  Matrix_translate(p3->center_mat, -p3->xcenter,-p3->ycenter,-p3->zcenter);
  Plot3D_maketransform(p3);
  return p3;
}

/* ---------------------------------------------------------------------
   delete_Plot3D(Plot3D *p3)

   Deletes a 3D plot
   --------------------------------------------------------------------- */

void delete_Plot3D(Plot3D *p3) {
  if (p3) {
    delete_Matrix(p3->view_mat);
    delete_Matrix(p3->model_mat);
    delete_Matrix(p3->trans_mat);
    free((char *) p3);
  }
}

/* ---------------------------------------------------------------------
   Plot3D *Plot3D_copy(Plot3D *p3)

   This makes a copy of the 3D plot structure and returns a pointer to it.
   --------------------------------------------------------------------- */

Plot3D *Plot3D_copy(Plot3D *p3) {
  Plot3D *c3;
  if (p3) {
    c3 = (Plot3D *) malloc(sizeof(Plot3D));
    if (c3) {
      c3->frame = p3->frame;
      c3->view_xmin = p3->view_xmin;
      c3->view_ymin = p3->view_ymin;
      c3->view_xmax = p3->view_xmax;
      c3->view_ymax = p3->view_ymax;
      c3->xmin = p3->xmin;
      c3->ymin = p3->ymin;
      c3->zmin = p3->zmin;
      c3->xmax = p3->xmax;
      c3->ymax = p3->ymax;
      c3->zmax = p3->zmax;
      c3->xcenter = p3->xcenter;
      c3->ycenter = p3->ycenter;
      c3->zcenter = p3->zcenter;
      c3->fovy = p3->fovy;
      c3->aspect = p3->aspect;
      c3->znear = p3->znear;
      c3->zfar = p3->zfar;
      c3->center_mat = Matrix_copy(p3->center_mat);
      c3->model_mat = Matrix_copy(p3->model_mat);
      c3->view_mat = Matrix_copy(p3->view_mat);
      c3->fullmodel_mat = Matrix_copy(p3->fullmodel_mat);
      c3->trans_mat = Matrix_copy(p3->trans_mat);
      c3->lookatz = p3->lookatz;
      c3->xshift = p3->xshift;
      c3->yshift = p3->yshift;
      c3->zoom = p3->zoom;
      c3->width = p3->width;
      c3->height = p3->height;
      c3->pers_mode = p3->pers_mode;
    }
    return c3;
  } else {
    return (Plot3D *) 0;
  }
}

/* ----------------------------------------------------------------------
   Plot3D_clear(Plot3D *p3, Pixel bgcolor)

   Clear the pixel and zbuffer only for the view region of this image.
   ---------------------------------------------------------------------- */
void
Plot3D_clear(Plot3D *p3, Pixel bgcolor) {
  int i,j;

  for (i = p3->view_xmin; i < p3->view_xmax; i++)
    for (j = p3->view_ymin; j < p3->view_ymax; j++) {
      p3->frame->pixels[j][i] = bgcolor;
      p3->frame->zbuffer[j][i] = ZMIN;
    }
}

/* ---------------------------------------------------------------------
   Plot3D_maketransform(Plot3D *p3)

   This function builds the total 3D transformation matrix from a
   collection of components.

   Trans = View * Rotation * Center

   Where View is the viewing transformation matrix, Rotation is the
   model rotation matrix, Center is the translation matrix used to
   center the Model at the origin.
   --------------------------------------------------------------------- */

void
Plot3D_maketransform(Plot3D *p3) {

  Matrix_multiply(p3->model_mat,p3->center_mat, p3->fullmodel_mat);
  Matrix_multiply(p3->view_mat,p3->fullmodel_mat, p3->trans_mat);
}
   
/* ---------------------------------------------------------------------
   Plot3D_perspective(Plot3D *p3, double fovy, double znear, double zfar)

   Sets up the perspective viewing transformation.   Assumes "lookat"
   has already been called.
   --------------------------------------------------------------------- */

void
Plot3D_perspective(Plot3D *p3, double fovy, double znear, double zfar) {
  double theta;
  double mat[16];

  p3->fovy = fovy;
  p3->znear = znear;
  p3->zfar = zfar;

  theta = 3.1415926*fovy/180.0;

  Matrix_identity(mat);
  mat[0] = cos(theta/2.0)/(sin(theta/2.0)*p3->aspect);
  mat[5] = cos(theta/2.0)/(sin(theta/2.0));
  mat[10] = -(zfar + znear)/(zfar-znear);
  mat[14] = -1.0;
  mat[11] = -(2*zfar*znear)/(zfar - znear);
  mat[15] = 0.0;

  /* Update the view transformation matrix */
  
  Matrix_multiply(mat,p3->view_mat,p3->view_mat);

  /* Update the global transformation matrix */
  
  Plot3D_maketransform(p3);
  p3->pers_mode = PERSPECTIVE;

}

/* ---------------------------------------------------------------------
   Plot3D_lookat(Plot3D *p3, double z)

   A greatly simplified version of (lookat).  Specifies the position
   of the viewpoint. (can be used for moving image in or out).

   Destroys the current viewing transformation matrix, so it will have
   to be recalculated.
   --------------------------------------------------------------------- */
  
void
Plot3D_lookat(Plot3D *p3, double z) {
  if (p3) {
    Matrix_translate(p3->view_mat, 0,0,-z);
    p3->lookatz = z;
    Plot3D_maketransform(p3);
  }
}

/* -------------------------------------------------------------------------
   Plot3D_autoperspective(Plot3D *p3, double fovy)

   Automatically figures out a semi-decent viewpoint given the
   min,max parameters currently set for this image
   ------------------------------------------------------------------------- */

void
Plot3D_autoperspective(Plot3D *p3, double fovy) {

  /* Make a perspective transformation matrix for this system */

  double  zfar;
  double  znear;
  double  d, dmax;
  double  cx,cy,cz;
  double  xmin,xmax,ymin,ymax,zmin,zmax;

  xmin = p3->xmin;
  ymin = p3->ymin;
  zmin = p3->zmin;
  xmax = p3->xmax;
  ymax = p3->ymax;
  zmax = p3->zmax;
  cx = p3->xcenter;
  cy = p3->ycenter;
  cz = p3->zcenter;
  
  /* Calculate longest point from center point */

  dmax = (xmin-cx)*(xmin-cx) + (ymin-cy)*(ymin-cy) + (zmin-cz)*(zmin-cz);
  d = (xmax-cx)*(xmax-cx) + (ymin-cy)*(ymin-cy) + (zmin-cz)*(zmin-cz);
  if (d > dmax) dmax = d;
  d = (xmin-cx)*(xmin-cx) + (ymax-cy)*(ymax-cy) + (zmin-cz)*(zmin-cz);
  if (d > dmax) dmax = d;
  d = (xmax-cx)*(xmax-cx) + (ymax-cy)*(ymax-cy) + (zmin-cz)*(zmin-cz);
  if (d > dmax) dmax = d;
  d = (xmin-cx)*(xmin-cx) + (ymin-cy)*(ymin-cy) + (zmax-cz)*(zmax-cz);
  if (d > dmax) dmax = d;
  d = (xmax-cx)*(xmax-cx) + (ymin-cy)*(ymin-cy) + (zmax-cz)*(zmax-cz);
  if (d > dmax) dmax = d;
  d = (xmin-cx)*(xmin-cx) + (ymax-cy)*(ymax-cy) + (zmax-cz)*(zmax-cz);
  if (d > dmax) dmax = d;
  d = (xmax-cx)*(xmax-cx) + (ymax-cy)*(ymax-cy) + (zmax-cz)*(zmax-cz);
  if (d > dmax) dmax = d;

  dmax = sqrt(dmax);
  d = p3->lookatz;
  
  znear = d - dmax;
  zfar = znear+1.5*dmax;
  Plot3D_perspective(p3, fovy,znear,zfar);
  
}

   
/* ---------------------------------------------------------------------
   Plot3D_ortho(Plot3D *p3, double left, double right, double bottom, double top)

   Sets up an orthographic viewing transformation.   
   --------------------------------------------------------------------- */

void
Plot3D_ortho(Plot3D *p3, double left, double right, double bottom, double top) {


  Matrix_identity(p3->view_mat);
  p3->view_mat[0] = (2.0/(right - left))/p3->aspect;
  p3->view_mat[5] = 2.0/(top - bottom);
  p3->view_mat[10] = -1;
  p3->view_mat[15] = 1.0;
  p3->view_mat[3] = -(right+left)/(right-left);
  p3->view_mat[7] = -(top+bottom)/(top-bottom);

  /* Update the global transformation matrix */
  
  Plot3D_maketransform(p3);
  p3->pers_mode = ORTHO;
  p3->ortho_left = left;
  p3->ortho_right = right;
  p3->ortho_bottom = bottom;
  p3->ortho_top = top;

}

/* ---------------------------------------------------------------------
   Plot3D_autoortho(Plot3D *p3)

   Automatically pick an orthographic projection that's probably
   pretty good.
   --------------------------------------------------------------------- */

void
Plot3D_autoortho(Plot3D *p3) {

  /* Make a perspective transformation matrix for this system */

  double  d, dmax;
  double  cx,cy,cz;
  double  xmin,xmax,ymin,ymax,zmin,zmax;

  xmin = p3->xmin;
  ymin = p3->ymin;
  zmin = p3->zmin;
  xmax = p3->xmax;
  ymax = p3->ymax;
  zmax = p3->zmax;
  cx = p3->xcenter;
  cy = p3->ycenter;
  cz = p3->zcenter;
  
  /* Calculate longest point from center point */

  dmax = (xmin-cx)*(xmin-cx) + (ymin-cy)*(ymin-cy) + (zmin-cz)*(zmin-cz);
  d = (xmax-cx)*(xmax-cx) + (ymin-cy)*(ymin-cy) + (zmin-cz)*(zmin-cz);
  if (d > dmax) dmax = d;
  d = (xmin-cx)*(xmin-cx) + (ymax-cy)*(ymax-cy) + (zmin-cz)*(zmin-cz);
  if (d > dmax) dmax = d;
  d = (xmax-cx)*(xmax-cx) + (ymax-cy)*(ymax-cy) + (zmin-cz)*(zmin-cz);
  if (d > dmax) dmax = d;
  d = (xmin-cx)*(xmin-cx) + (ymin-cy)*(ymin-cy) + (zmax-cz)*(zmax-cz);
  if (d > dmax) dmax = d;
  d = (xmax-cx)*(xmax-cx) + (ymin-cy)*(ymin-cy) + (zmax-cz)*(zmax-cz);
  if (d > dmax) dmax = d;
  d = (xmin-cx)*(xmin-cx) + (ymax-cy)*(ymax-cy) + (zmax-cz)*(zmax-cz);
  if (d > dmax) dmax = d;
  d = (xmax-cx)*(xmax-cx) + (ymax-cy)*(ymax-cy) + (zmax-cz)*(zmax-cz);
  if (d > dmax) dmax = d;

  dmax = sqrt(dmax);

  Plot3D_ortho(p3,-dmax,dmax,-dmax,dmax);
  
}



/* -------------------------------------------------------------------------
   Plot3D_setview(Plot3D *p3, int vxmin, int vymin, int vxmax, int vymax)

   Sets the viewport for this 3D graph.  Will recalculate all of the
   local viewing transformation matrices accordingly.
   ------------------------------------------------------------------------- */
void
Plot3D_setview(Plot3D *p3, int vxmin, int vymin, int vxmax, int vymax) {
  if (p3) {
    if ((vxmin > vxmax) || (vymin >vymax)) return;
    p3->view_xmin = vxmin;
    p3->view_ymin = vymin;
    p3->view_xmax = vxmax;
    p3->view_ymax = vymax;
    p3->width = (vxmax - vxmin);
    p3->height = (vymax - vymin);
    p3->aspect = (double) p3->width/(double) p3->height;

    /* Fix up the viewing transformation matrix */

    if (p3->pers_mode == PERSPECTIVE) {
      Plot3D_lookat(p3,p3->lookatz);
      Plot3D_perspective(p3,p3->fovy,p3->znear,p3->zfar);
    } else {
      Plot3D_ortho(p3,p3->ortho_left,p3->ortho_right,p3->ortho_bottom, p3->ortho_top);
    }
    FrameBuffer_setclip(p3->frame,vxmin,vymin,vxmax,vymax);
  }
}

/* ---------------------------------------------------------------------------
   Plot2D_start(Plot2D *p3)

   Set up viewing region and other parameters for this image.
   --------------------------------------------------------------------------- */

void
Plot3D_start(Plot3D *p3) {
  if (p3)
    FrameBuffer_setclip(p3->frame, p3->view_xmin,p3->view_ymin,p3->view_xmax, p3->view_ymax);

}

/* -------------------------------------------------------------------------
   Plot3D_plot(Plot3D *p3, double x, double y, double z, Pixel Color)

   Plot a 3D point
   ------------------------------------------------------------------------- */
   
void
Plot3D_plot(Plot3D *p3, double x, double y, double z, Pixel color) { 

  GL_Vector t;
  int      ix, iy;
  double   invw;
  FrameBuffer *f;
  
  /* Perform a transformation */

  Matrix_transform4(p3->trans_mat,x,y,z,1,&t);

  /* Scale the coordinates into unit cube */
  
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
#ifdef GL_DEBUG
  fprintf(stdout,"t.x = %g,   t.y = %g,    t.z = %g\n", t.x,t.y,t.z);
#endif
  /* Calculate the x and y coordinates */

  ix = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5); 
  iy = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5); 

  if ((ix >= 0) && (ix < p3->width) &&
      (iy >= 0) && (ix < p3->height)) {
    ix += p3->view_xmin;
    iy += p3->view_ymin;
    f = p3->frame;
    if (t.z <= f->zbuffer[iy][ix]) {
      f->pixels[iy][ix] = color;
      f->zbuffer[iy][ix] = t.z;
    }
  }
}

/* ----------------------------------------------------------------------
   Plot3D_rotx(Plot3D *p3, double deg)

   Rotate the model around its x axis.
   ---------------------------------------------------------------------- */

void
Plot3D_rotx(Plot3D *p3, double deg) {
  double temp[16];

  Matrix_rotatex(temp,deg);         /* Construct a x rotation matrix */
  Matrix_multiply(p3->model_mat,temp,p3->model_mat);
  Plot3D_maketransform(p3);

}

/* ----------------------------------------------------------------------
   Plot3D_roty(Plot3D *p3, double deg)

   Rotate the model around its y axis.
   ---------------------------------------------------------------------- */

void
Plot3D_roty(Plot3D *p3, double deg) {
  double temp[16];

  Matrix_rotatey(temp,deg);         /* Construct a y rotation matrix */
  Matrix_multiply(p3->model_mat,temp,p3->model_mat);
  Plot3D_maketransform(p3);

}

/* ----------------------------------------------------------------------
   Plot3D_rotz(Plot3D *p3, double deg)

   Rotate the model around its z axis.
   ---------------------------------------------------------------------- */

void
Plot3D_rotz(Plot3D *p3, double deg) {
  double temp[16];

  Matrix_rotatez(temp,deg);         /* Construct a z rotation matrix */
  Matrix_multiply(p3->model_mat,temp,p3->model_mat);
  Plot3D_maketransform(p3);

}


/* ----------------------------------------------------------------------
   Plot3D_rotd(Plot3D *p3, double deg)

   Rotate the model down
   ---------------------------------------------------------------------- */

void
Plot3D_rotd(Plot3D *p3, double deg) {
  double temp[16];

  Matrix_rotatex(temp,deg);         /* Construct a x rotation matrix */
  Matrix_multiply(temp, p3->model_mat,p3->model_mat);
  Plot3D_maketransform(p3);

}
    

/* ----------------------------------------------------------------------
   Plot3D_rotu(Plot3D *p3, double deg)

   Rotate the model up
   ---------------------------------------------------------------------- */

void
Plot3D_rotu(Plot3D *p3, double deg) {
  double temp[16];

  Matrix_rotatex(temp,-deg);         /* Construct a x rotation matrix */
  Matrix_multiply(temp,p3->model_mat,p3->model_mat);
  Plot3D_maketransform(p3);

}


/* ----------------------------------------------------------------------
   Plot3D_rotr(Plot3D *p3, double deg)

   Rotate the model down
   ---------------------------------------------------------------------- */

void
Plot3D_rotr(Plot3D *p3, double deg) {
  double temp[16];

  Matrix_rotatey(temp,deg);         /* Construct a y rotation matrix */
  Matrix_multiply(temp, p3->model_mat,p3->model_mat);
  Plot3D_maketransform(p3);

}
    

/* ----------------------------------------------------------------------
   Plot3D_rotl(Plot3D *p3, double deg)

   Rotate the model left
   ---------------------------------------------------------------------- */

void
Plot3D_rotl(Plot3D *p3, double deg) {
  double temp[16];

  Matrix_rotatey(temp,-deg);         /* Construct a y rotation matrix */
  Matrix_multiply(temp,p3->model_mat,p3->model_mat);
  Plot3D_maketransform(p3);

}


/* ----------------------------------------------------------------------
   Plot3D_rotc(Plot3D *p3, double deg)

   Rotate the model around center point
   ---------------------------------------------------------------------- */

void
Plot3D_rotc(Plot3D *p3, double deg) {
  double temp[16];

  Matrix_rotatez(temp,-deg);         /* Construct a z rotation matrix */
  Matrix_multiply(temp,p3->model_mat,p3->model_mat);
  Plot3D_maketransform(p3);
}

/* -------------------------------------------------------------------------
   Plot3D_zoom(Plot3D *p3, double percent)

   Zooms in or out the current image.   percent defines a percentage of
   zoom.

   Zooming is actually done by adjusting the perspective field of view
   instead of scaling the model or moving in the viewpoint.    This
   seems to work the best.
   ------------------------------------------------------------------------- */

void
Plot3D_zoom(Plot3D *p3, double percent) {

  double scale;
  double dx;
  if (percent <= 0) return;
  scale = percent/100.0;

  dx = (1.0/scale - 1.0)/(2*p3->zoom); /* Don't even ask where this came from */
  p3->xshift += dx;
  p3->yshift += dx;
  p3->zoom = p3->zoom*scale;

#ifdef OLD
  p3->fovy = p3->fovy*scale;
  if (p3->fovy > 170.0) p3->fovy = 170.0;
  if (p3->fovy == 0) p3->fovy = 0.0001;
  Plot3D_lookat(p3,p3->lookatz);
  Plot3D_perspective(p3,p3->fovy,p3->znear,p3->zfar);
#endif
}

/* --------------------------------------------------------------------------
   Plot3D_left(Plot3D *p3, double s)

   Shifts the image to the left by s units.   This is a little funky.

   s is scaled so that s = 100 equals one full screen.
   -------------------------------------------------------------------------- */
void
Plot3D_left(Plot3D *p3, double s) {
  p3->xshift -= (s/100.0)/p3->zoom;
}

/* --------------------------------------------------------------------------
   Plot3D_right(Plot3D *p3, double s)

   Shifts the image to the right by s units. 

   s is scaled so that s = 100 equals one full screen.
   -------------------------------------------------------------------------- */
void
Plot3D_right(Plot3D *p3, double s) {
  p3->xshift += (s/100.0)/p3->zoom;
}

/* --------------------------------------------------------------------------
   Plot3D_up(Plot3D *p3, double s)

   Shifts the image up left by s units.

   s is scaled so that s = 100 equals one full screen.
   -------------------------------------------------------------------------- */
void
Plot3D_up(Plot3D *p3, double s) {
  p3->yshift += (s/100.0)/p3->zoom;
}

/* --------------------------------------------------------------------------
   Plot3D_down(Plot3D *p3, double s)

   Shifts the image down by s units.  

   s is scaled so that s = 100 equals one full screen.
   -------------------------------------------------------------------------- */
void
Plot3D_down(Plot3D *p3, double s) {
  p3->yshift -= (s/100.0)/p3->zoom;
}

/* -------------------------------------------------------------------------
   Plot3D_center(Plot3D *p3, double cx, double cy)

   Centers the image on a point in the range (0,0) - (100,100)
   ------------------------------------------------------------------------- */
void
Plot3D_center(Plot3D *p3, double cx, double cy) {
  Plot3D_left(p3,cx-50);
  Plot3D_down(p3,cy-50);
}



/***************************************************************************
 *                       3d Primitives                                     *
 ***************************************************************************/

/* -------------------------------------------------------------------------
   Plot3D_horizontal(Plot3D *p3, int xmin, int xmax, int y, double z1, double z2, Pixel color)

   Draws a "Horizontal" line on the framebuffer between two screen coordinates,
   but also supplies z-values and zbuffering.   This function probably isn't
   too useful by itself, but will be used by a number of other primitives.
   -------------------------------------------------------------------------- */

void Plot3D_horizontal(Plot3D *p3, int xmin, int xmax, int y, Zvalue z1, Zvalue z2, Pixel color) {
  Pixel  *p;
  FrameBuffer *f;
  int     i;
  Zvalue  *zbuf,z,mz;
  int     startx, endx;

  f = p3->frame;
  if ((y < f->ymin) || (y >= f->ymax)) return;
  if (xmin > f->xmax) return;
  if (xmin < f->xmin) startx = f->xmin;
  else startx = xmin;
  if (xmax < f->xmin) return;
  if (xmax >= f->xmax) endx = f->xmax - 1;
  else endx = xmax;

  /* Calculate z slope */

  if (xmax != xmin) {
    mz = (Zvalue) ((double) (z2 - z1)/(double) (xmax - xmin));
  } else {
    mz = 0;
  }

  /* Draw it */
  
  p = &f->pixels[y][startx];
  zbuf = &f->zbuffer[y][startx];
  z = (Zvalue) (mz*(startx-xmin) + z1);
  for (i = startx; i <= endx; i++, p++, zbuf++,z+=mz) {
    if (z <= *zbuf) {
      *p = color;
      *zbuf = z;
    }
  }
}


/* -------------------------------------------------------------------------
   Plot3D_vertical(Plot3D *p3, int ymin, int ymax, int x, double z1, double z2, Pixel color)

   Draws a "Vertical" line on the framebuffer between two screen coordinates,
   but also supplies z-values and zbuffering.   This function probably isn't
   too useful by itself, but will be used by a number of other primitives.
   -------------------------------------------------------------------------- */

void Plot3D_vertical(Plot3D *p3, int ymin, int ymax, int x, Zvalue z1, Zvalue z2, Pixel color) {
  Pixel  *p;
  FrameBuffer *f;
  int     i;
  Zvalue  *zbuf,z,mz;
  int     starty, endy;

  f = p3->frame;
  if ((x < f->xmin) || (x >= f->xmax)) return;
  if (ymin >= f->ymax) return;
  if (ymin < f->ymin) starty = f->ymin;
  else starty = ymin;
  if (ymax < f->ymin) return;
  if (ymax >= f->ymax) endy = f->ymax - 1;
  else endy = ymax;

  /* Calculate z slope */

  mz = (double) (z2 - z1)/(double) (ymax - ymin);

  /* Draw it */
  
  p = &f->pixels[starty][x];
  zbuf = &f->zbuffer[starty][x];
  for (i = starty; i <= endy; i++, p+=f->width, zbuf+=f->width) {
    z = (Zvalue) (mz*(i-ymin) + z1);
    if (z <= *zbuf) {
      *p = color;
      *zbuf = z;
    }
  }
}

/* -------------------------------------------------------------------------------
   Plot3D_linetransform(Plot3D *p3, int x1, int y1, Zvalue z1,
                                    int x2, int y2, Zvalue z2, Pixel c)

   Draw a 3D line between points that have already been transformed into
   3D space.

   Uses a Bresenham line algorithm, but with linear interpolation between
   Zvalues.
   ------------------------------------------------------------------------------- */
   
void
Plot3D_linetransform(Plot3D *p3, int x1, int y1, Zvalue z1, int x2, int y2, Zvalue z2, Pixel c) {

  int       orig_x1, orig_y1, orig_x2,orig_y2;
  Zvalue    zt;
  
  /* Bresenham line drawing parameters */
  FrameBuffer *f;
  int  dx,dy,dxneg,dyneg, inc1,inc2,di;
  int  x, y, xpixels, ypixels, xt, yt;
  Pixel *p;
  double m;
  int  end1 = 0, end2 = 0;
  Zvalue  *zbuf,mz,z;

  f = p3->frame;
    
  /* Need to figure out where in the heck this line is */

  dx = x2 - x1;
  dy = y2 - y1;

  if ((dx == 0) && (dy == 0)) {
    if ((x1 < f->xmin) || (x1 >= f->xmax) ||
	(y1 < f->ymin) || (y1 >= f->ymax)) return;
    if (z1 <= f->zbuffer[y1][x1]) {
      f->pixels[y1][x1] = c;
    }
    return;
  }
  if (dx == 0) {
    /* Draw a Vertical Line */
    if (y1 < y2)
      Plot3D_vertical(p3,y1,y2,x1,z1,z2,c);
    else
      Plot3D_vertical(p3,y2,y1,x1,z2,z1,c);
    return;
  }
  if (dy == 0) {
    /* Draw a Horizontal Line */
    if (x1 < x2)
      Plot3D_horizontal(p3,x1,x2,y1,z1,z2,c);
    else
      Plot3D_horizontal(p3,x2,x1,y1,z2,z1,c);
    return;
  }

  /* Figure out where in the heck these lines are using the
     Cohen-Sutherland Line Clipping Scheme. */

  end1 = ((x1 - f->xmin) < 0) |
    (((f->xmax- 1 - x1) < 0) << 1) |
    (((y1 - f->ymin) < 0) << 2) |
    (((f->ymax-1 - y1) < 0) << 3);

  end2 = ((x2 - f->xmin) < 0) |
    (((f->xmax-1 - x2) < 0) << 1) |
    (((y2 - f->ymin) < 0) << 2) |
    (((f->ymax-1 - y2) < 0) << 3);

  if (end1 & end2) return;      /* Nope : Not visible */
  
  /* Make sure points have a favorable orientation */
  
  if (x1 > x2) {
    xt = x1;
    x1 = x2;
    x2 = xt;
    yt = y1;
    y1 = y2;
    y2 = yt;
    zt = z1;
    z1 = z2;
    z2 = zt;
  }

  /* Save original points before we clip them off */
  orig_x1 = x1;
  orig_y1 = y1;
  orig_x2 = x2;
  orig_y2 = y2;
  
  /* Clip against the boundaries */
  m = (y2 - y1)/(double) (x2-x1);
  if (x1 < f->xmin) {
    y1 = (f->xmin - x1)*m + y1;
    x1 = f->xmin;
  }
  if (x2 >= f->xmax) {
    y2 = (f->xmax -1 -x1)*m + y1;
    x2 = f->xmax - 1;
  }

  if (y1 > y2) {
    xt = x1;
    x1 = x2;
    x2 = xt;
    yt = y1;
    y1 = y2;
    y2 = yt;
    zt = z1;
    z1 = z2;
    z2 = zt;

    /* Swap original points */

    xt = orig_x1;
    orig_x1 = orig_x2;
    orig_x2 = xt;
    yt = orig_y1;
    orig_y1 = orig_y2;
    orig_y2 = yt;
  }

  m = 1/m;
  if (y1 < f->ymin) {
    x1 = (f->ymin - y1)*m + x1;
    y1 = f->ymin;
  }
  if (y2 >= f->ymax) {
    x2 = (f->ymax-1-y1)*m + x1;
    y2 = f->ymax-1;
  }

  if ((x1 < f->xmin) || (x1 >= f->xmax) || (y1 < f->ymin) || (y1 >= f->ymax) ||
      (x2 < f->xmin) || (x2 >= f->xmax) || (y2 < f->ymin) || (y2 >= f->ymax)) return;

  dx = x2 - x1;
  dy = y2 - y1;
  xpixels = f->width;
  ypixels = f->height;
  
  dxneg = (dx < 0) ? 1 : 0;
  dyneg = (dy < 0) ? 1 : 0;
  
  dx = abs(dx);
  dy = abs(dy);
  if (dx >= dy) {
    /* Slope between -1 and 1. */
    mz = (z2 - z1)/(orig_x2 - orig_x1);    /* Z interpolation slope */
    if (dxneg) {
      x = x1;
      y = y1;
      x1 = x2;
      y1 = y2;
      x2 = x;
      y2 = y;
      dyneg = !dyneg;
    }
    inc1 = 2*dy;
    inc2 = 2*(dy-dx);
    di = 2*dy-dx;

    /* Draw a line using x as independent variable */
    
    p = &f->pixels[y1][x1];
    zbuf = &f->zbuffer[y1][x1];
    x = x1;
    while (x <= x2) {
      /* Do a z-buffer check */
      z = mz*(x-orig_x1)+z1;
      if (z <= *zbuf){
	*p = c;
	*zbuf = z;
      }
      p++;
      zbuf++;
      if (di < 0) {
	di = di + inc1;
      } else {
	if (dyneg) {
	  p = p - xpixels;
	  zbuf = zbuf - xpixels;
	  di = di + inc2;
	} else {
	  p = p + xpixels;
	  zbuf = zbuf + xpixels;
	  di = di + inc2;
	}
      }
      x++;
    }
  } else {
    /* Slope < -1 or > 1 */
    mz = (z2 - z1)/(double) (orig_y2 - orig_y1);
    if (dyneg) {
      x = x1;
      y = y1;
      x1 = x2;
      y1 = y2;
      x2 = x;
      y2 = y;
      dxneg = !dxneg;
    }
    inc1 = 2*dx;
    inc2 = 2*(dx-dy);
    di = 2*dx-dy;

    /* Draw a line using y as independent variable */
    
    p = &f->pixels[y1][x1];
    zbuf = &f->zbuffer[y1][x1];
    y = y1;
    while (y <= y2) {
      /* Do a z-buffer check */
      z = mz*(y-orig_y1)+z1;
      if (z <= *zbuf) {
	*p = c;
	*zbuf = z;
      }
      p = p + xpixels;
      zbuf = zbuf + xpixels;
      if (di < 0) {
	di = di + inc1;
      } else {
	if (dxneg) {
	  p = p - 1;
	  zbuf = zbuf - 1;
	  di = di + inc2;
	} else {
	  p = p + 1;
	  zbuf = zbuf + 1;
	  di = di + inc2;
	}
      }
      y++;
    }
  }    
}

/* ---------------------------------------------------------------------------
   Plot3D_line(Plot3D *p3, double x1, double y1, double z1, double x2, double y2, double z2,int color)

   Draws a line in 3D space.   This is done as follows (for lack of a better
   method).

   1.  The points (x1,y1,z1) and (x2,y2,z2) are transformed into screen coordinates
   2.  We draw the line using a modified Bresenham line algorithm.
   3.  Zbuffer values are linearly interpolated between the two points.
   ---------------------------------------------------------------------------- */

void
Plot3D_line(Plot3D *p3, double fx1, double fy1, double fz1, double fx2, double fy2,
	    double fz2, Pixel c) {

  /* 3D Transformation parameters */
  GL_Vector t;
  double    invw;
  int       x1,y1,x2,y2;
  Zvalue    z1,z2;
  
  /* Transform the two points into screen coordinates */
  
  Matrix_transform4(p3->trans_mat,fx1,fy1,fz1,1,&t);  /* Point 1 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  x1 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  y1 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  z1 = t.z;
  
  Matrix_transform4(p3->trans_mat,fx2,fy2,fz2,1,&t);  /* Point 2 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  x2 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  y2 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  z2 = t.z;
  Plot3D_linetransform(p3,x1,y1,z1,x2,y2,z2,c);
}


/* -------------------------------------------------------------------------
   Plot3D_triangle(Plot3D *p3, double x1, double y1, double z1,
                               double x2, double y2, double z2,
			       double x3, double y3, double z3,
			       Pixel fillcolor)

   This function draws a 3D z-buffered outline triangle.
   -------------------------------------------------------------------------- */

void Plot3D_triangle(Plot3D *p3, double x1, double y1, double z1,
		     double x2, double y2, double z2,
		     double x3, double y3, double z3, Pixel color) {

  int        tx1, tx2, tx3, ty1, ty2, ty3;
  Zvalue     tz1, tz2, tz3;
  GL_Vector  t;
  double     invw;
  
  /* Transform the three points into screen coordinates */

  Matrix_transform4(p3->trans_mat,x1,y1,z1,1,&t);  /* Point 1 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx1 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty1 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz1 = (Zvalue) t.z;
  
  Matrix_transform4(p3->trans_mat,x2,y2,z2,1,&t);  /* Point 2 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx2 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty2 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz2 = (Zvalue) t.z;

  Matrix_transform4(p3->trans_mat,x3,y3,z3,1,&t);  /* Point 3 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx3 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty3 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz3 = (Zvalue) t.z;


  Plot3D_linetransform(p3,tx1,ty1,tz1,tx2,ty2,tz2,color);
  Plot3D_linetransform(p3,tx1,ty1,tz1,tx3,ty3,tz3,color);
  Plot3D_linetransform(p3,tx2,ty2,tz2,tx3,ty3,tz3,color);
}


/* -------------------------------------------------------------------------
   Plot3D_solidtriangletransform(Plot3D *p3, int tx1, int ty2, Zvalue tz1,
                                 int tx2, int ty2, Zvalue tz2,
				 int tx3, int ty3, Zvalue tz3, Pixel color) 

   This function draws a 3D z-buffered filled triangle.   Assumes three
   points have already been transformed into screen coordinates.

   General idea :
         1.   Transform the three points into screen coordinates
	 2.   Order three points vertically on screen.
	 3.   Check for degenerate cases (where 3 points are colinear).
	 4.   Fill in the resulting triangle using horizontal lines.
   -------------------------------------------------------------------------- */

void Plot3D_solidtriangletransform(Plot3D *p3, int tx1, int ty1, Zvalue tz1,
				   int tx2, int ty2, Zvalue tz2,
				   int tx3, int ty3, Zvalue tz3, Pixel color) {
  int        tempx, tempy;
  Zvalue     tempz;
  double     m1,m2,m3, mz1, mz2, mz3;
  int        y;
  int        ix1, ix2;
  Zvalue     zz1, zz2;
  FrameBuffer *f;
  register   double fy1,fy2;
  register   Zvalue fz1,fz2;

  f = p3->frame;

  /* Check for degenerate cases here */

  if ((ty1 == ty2) && (ty2 == ty3)) {
    if (tx2 < tx1) {   /* Swap points 1 and 2 if 2 is higher */
      tempx = tx1;
      tempz = tz1;
      tx1 = tx2;
      tz1 = tz2;
      tx2 = tempx;
      tz2 = tempz;
    }
    if (tx3 < tx1) {  /* Swap points 1 and 3 if 3 is higher */
      tempx = tx1;
      tempz = tz1;
      tx1 = tx3;
      tz1 = tz3;
      tx3 = tempx;
      tz3 = tempz;
    }
    if (tx3 < tx2) { /* Swap points 2 and 3 if 3 is higher */ 
      tempx = tx2;
      tempz = tz2;
      tx2 = tx3;
      tz2 = tz3;
      tx3 = tempx;
      tz3 = tempz;
    }

    /* Points are aligned horizontally.   Handle as a special case */
    /* Just draw three lines using the outline color */

    Plot3D_horizontal(p3,tx1,tx2,ty1,tz1,tz3,color);

    /*    Plot3D_linetransform(p3,tx1,ty1,tz1,tx2,ty2,tz2,color);
    Plot3D_linetransform(p3,tx1,ty1,tz1,tx3,ty3,tz3,color);
    Plot3D_linetransform(p3,tx2,ty2,tz2,tx3,ty3,tz3,color);
    */

    return;
  }

  /* Figure out which point has the greatest "y" value */

  if (ty2 > ty1) {   /* Swap points 1 and 2 if 2 is higher */
    tempx = tx1;
    tempy = ty1;
    tempz = tz1;
    tx1 = tx2;
    ty1 = ty2;
    tz1 = tz2;
    tx2 = tempx;
    ty2 = tempy;
    tz2 = tempz;
  }
  if (ty3 > ty1) {  /* Swap points 1 and 3 if 3 is higher */
    tempx = tx1;
    tempy = ty1;
    tempz = tz1;
    tx1 = tx3;
    ty1 = ty3;
    tz1 = tz3;
    tx3 = tempx;
    ty3 = tempy;
    tz3 = tempz;
  }
  if (ty3 > ty2) { /* Swap points 2 and 3 if 3 is higher */ 
    tempx = tx2;
    tempy = ty2;
    tempz = tz2;
    tx2 = tx3;
    ty2 = ty3;
    tz2 = tz3;
    tx3 = tempx;
    ty3 = tempy;
    tz3 = tempz;
  }

  /* Points are now order so that t_1 is the highest point, t_2 is the
     middle point, and t_3 is the lowest point */

    if (ty2 < ty1) {
      /* First process line segments between (x1,y1)-(x2,y2)
	 And between (x1,y1),(x3,y3) */

      m1 = (double) (tx2 - tx1)/(double) (ty2 - ty1);
      m2 = (double) (tx3 - tx1)/(double) (ty3 - ty1);
      mz1 = (tz2 - tz1)/(double) (ty2 - ty1);
      mz2 = (tz3 - tz1)/(double) (ty3 - ty1);

      y = ty1;
      fy1 = m1*(y-ty1)+0.5 + tx1;
      fy2 = m2*(y-ty1)+0.5 + tx1;
      fz1 = mz1*(y-ty1) + tz1;
      fz2 = mz2*(y-ty1) + tz1;
      while (y >= ty2) {
	/* Replace with bresenham scheme */
	/* Calculate x values from slope */
	ix1 = (int) fy1;
	ix2 = (int) fy2;
	zz1 = fz1;
	zz2 = fz2;
	fy1-= m1;
	fy2-= m2;
	fz1-= mz1;
	fz2-= mz2;
	if (ix1 > ix2) 
	  Plot3D_horizontal(p3,ix2,ix1,y,zz2,zz1,color);
	else
	  Plot3D_horizontal(p3,ix1,ix2,y,zz1,zz2,color);
	y--;
      }
    }
    if (ty3 < ty2) {
      /* Draw lower half of the triangle */
      m2 = (double) (tx3 - tx1)/(double) (ty3 - ty1);
      m3 = (double) (tx3 - tx2)/(double)(ty3 - ty2);
      mz2 = (tz3 - tz1)/(double) (ty3 - ty1);
      mz3 = (tz3 - tz2)/(double) (ty3 - ty2);
      y = ty2;
      while (y >= ty3) {
	ix1 = (int) (m3*(y-ty2)+0.5)+tx2;
	ix2 = (int) (m2*(y-ty1)+0.5)+tx1;
	zz1 = mz3*(y-ty2)+tz2;
	zz2 = mz2*(y-ty1)+tz1;
	if (ix1 > ix2)
	  Plot3D_horizontal(p3,ix2,ix1,y,zz2,zz1,color);
	else
	  Plot3D_horizontal(p3,ix1,ix2,y,zz1,zz2,color);
	y--;
      }
    }
}

/* -------------------------------------------------------------------------
   Plot3D_solidtriangle(Plot3D *p3, double x1, double y1, double z1,
                               double x2, double y2, double z2,
			       double x3, double y3, double z3,
			       Pixel color)

   This function draws a 3D z-buffered filled triangle.    Can be used to
   draw other primitives such as quadralaterals, etc...

   This function simply transforms the given points and calls
   Plot3D_SolidTriangleTransform().
   -------------------------------------------------------------------------- */

void Plot3D_solidtriangle(Plot3D *p3, double x1, double y1, double z1,
			  double x2, double y2, double z2,
			  double x3, double y3, double z3, Pixel color) {

  int        tx1, tx2, tx3, ty1, ty2, ty3;
  Zvalue     tz1, tz2, tz3;
  GL_Vector  t;
  double     invw;
  Matrix     a;
  register double     xshift, yshift, zoom, width, height, view_xmin, view_ymin;

  a = p3->trans_mat;
  xshift = p3->xshift;
  yshift = p3->yshift;
  zoom = p3->zoom;
  height = p3->height;
  width = p3->width;
  view_xmin = p3->view_xmin;
  view_ymin = p3->view_ymin;

  /* Transform the three points into screen coordinates */

  t.w = a[12]*x1 + a[13]*y1 + a[14]*z1 + a[15];
  invw = 1.0/t.w;
  t.x = (a[0]*x1 + a[1]*y1 + a[2]*z1 + a[3])*invw;
  t.y = (a[4]*x1 + a[5]*y1 + a[6]*z1 + a[7])*invw;
  t.z = (a[8]*x1 + a[9]*y1 + a[10]*z1 + a[11])*invw;

  tx1 = (int) ((t.x +xshift)*zoom*width + 0.5) + view_xmin; 
  ty1 = (int) ((t.y +yshift)*zoom*height + 0.5) + view_ymin;
  tz1 = (Zvalue) t.z;


  t.w = a[12]*x2 + a[13]*y2 + a[14]*z2 + a[15];
  invw = 1.0/t.w;
  t.x = (a[0]*x2 + a[1]*y2 + a[2]*z2 + a[3])*invw;
  t.y = (a[4]*x2 + a[5]*y2 + a[6]*z2 + a[7])*invw;
  t.z = (a[8]*x2 + a[9]*y2 + a[10]*z2 + a[11])*invw;
  tx2 = (int) ((t.x +xshift)*zoom*width + 0.5) + view_xmin; 
  ty2 = (int) ((t.y +yshift)*zoom*height + 0.5) + view_ymin;
  tz2 = (Zvalue) t.z;

  t.w = a[12]*x3 + a[13]*y3 + a[14]*z3 + a[15];
  invw = 1.0/t.w;
  t.x = (a[0]*x3 + a[1]*y3 + a[2]*z3 + a[3])*invw;
  t.y = (a[4]*x3 + a[5]*y3 + a[6]*z3 + a[7])*invw;
  t.z = (a[8]*x3 + a[9]*y3 + a[10]*z3 + a[11])*invw;
  tx3 = (int) ((t.x +xshift)*zoom*width + 0.5) + view_xmin; 
  ty3 = (int) ((t.y +yshift)*zoom*height + 0.5) + view_ymin;
  tz3 = (Zvalue) t.z;

  Plot3D_solidtriangletransform(p3,tx1,ty1,tz1,tx2,ty2,tz2,tx3,ty3,tz3,color); 
  
}    


/* -------------------------------------------------------------------------
   Plot3D_horizontalinterp(Plot3D *p3, int xmin, int xmax, int y,
                           double z1, double z2, Pixel c1, Pixel c2)

   Draws a "Horizontal" line on the framebuffer between two screen coordinates,
   but also supplies z-values and zbuffering.  Performs a color interpolation
   between c1 and c2.  This is primarily used by the SolidTriangleInterp()
   function to give the illusion of smooth surfaces.
   -------------------------------------------------------------------------- */

void Plot3D_horizontalinterp(Plot3D *p3, int xmin, int xmax, int y,
			     Zvalue z1, Zvalue z2, Pixel c1, Pixel c2) {
  Pixel  *p;
  FrameBuffer *f;
  int     i;
  Zvalue  *zbuf,z,mz;
  double   mc;
  int     startx, endx;
  double   invdx;
  
  f = p3->frame;
  if ((y < f->ymin) || (y >= f->ymax)) return;
  if (xmin >= f->xmax) return;
  if (xmin < f->xmin) startx = f->xmin;
  else startx = xmin;
  if (xmax < f->xmin) return;
  if (xmax >= f->xmax) endx = f->xmax - 1;
  else endx = xmax;

  /* Calculate z slope */
  if (xmax != xmin) {
    invdx = 1.0/(double) (xmax-xmin);
  } else {
    invdx = 0;
  }

  mz = (Zvalue) (z2 - z1)*invdx;

  /* Calculate c slope */

  mc = (double) (c2 - c1)*invdx;
    
  /* Draw it */
  
  p = &f->pixels[y][startx];
  zbuf = &f->zbuffer[y][startx];
  for (i = startx; i <= endx; i++, p++, zbuf++) {
    z = (Zvalue) (mz*(i-xmin) + z1);
    if (z <= *zbuf) {
      *p = (Pixel) (mc*(i-xmin)+c1);
      *zbuf = z;
    }
  }
}

/* -------------------------------------------------------------------------
   Plot3D_interptriangletransform(Plot3D *p3,
                                  int tx1, int ty2, Zvalue tz1, Pixel c1,
                                  int tx2, int ty2, Zvalue tz2, Pixel c2,
				  int tx3, int ty3, Zvalue tz3, Pixel c3)

   This function draws a 3D z-buffered filled triangle with color
   interpolation.   Assumes three points have already been transformed
   into screen coordinates.

   General idea :
         1.   Transform the three points into screen coordinates
	 2.   Order three points vertically on screen.
	 3.   Check for degenerate cases (where 3 points are colinear).
	 4.   Fill in the resulting triangle using horizontal lines.
	 5.   Colors are interpolated between end points
   -------------------------------------------------------------------------- */

void Plot3D_interptriangletransform(Plot3D *p3,
				    int tx1, int ty1, Zvalue tz1, Pixel c1,
				    int tx2, int ty2, Zvalue tz2, Pixel c2,
				    int tx3, int ty3, Zvalue tz3, Pixel c3) {
  int        tempx, tempy;
  Zvalue     tempz;
  double     m1,m2,m3, mz1, mz2, mz3;
  double     mc1,mc2,mc3;
  Pixel      ic1,ic2,tempc;
  int        y;
  int        ix1, ix2;
  Zvalue     zz1, zz2;
  FrameBuffer *f;
  
  f = p3->frame;

  /* Figure out which point has the greatest "y" value */

  if (ty2 > ty1) {   /* Swap points 1 and 2 if 2 is higher */
    tempx = tx1;
    tempy = ty1;
    tempz = tz1;
    tempc = c1;
    tx1 = tx2;
    ty1 = ty2;
    tz1 = tz2;
    c1 = c2;
    tx2 = tempx;
    ty2 = tempy;
    tz2 = tempz;
    c2 = tempc;
  }
  if (ty3 > ty1) {  /* Swap points 1 and 3 if 3 is higher */
    tempx = tx1;
    tempy = ty1;
    tempz = tz1;
    tempc = c1;
    tx1 = tx3;
    ty1 = ty3;
    tz1 = tz3;
    c1 = c3;
    tx3 = tempx;
    ty3 = tempy;
    tz3 = tempz;
    c3 = tempc;
  }
  if (ty3 > ty2) { /* Swap points 2 and 3 if 3 is higher */ 
    tempx = tx2;
    tempy = ty2;
    tempz = tz2;
    tempc = c2;
    tx2 = tx3;
    ty2 = ty3;
    tz2 = tz3;
    c2 = c3;
    tx3 = tempx;
    ty3 = tempy;
    tz3 = tempz;
    c3 = tempc;
  }

  /* Points are now order so that t_1 is the highest point, t_2 is the
     middle point, and t_3 is the lowest point */

  /* Check for degenerate cases here */

  if ((ty1 == ty2) && (ty2 == ty3)) {

    /* Points are aligned horizontally.   Handle as a special case */
    /* Just draw three lines using the outline color */

    if (tx2 > tx1)
      Plot3D_horizontalinterp(p3,tx1,tx2,ty1,tz1,tz2,c1,c2);
    else
      Plot3D_horizontalinterp(p3,tx2,tx1,ty1,tz2,tz1,c2,c1);
    if (tx3 > tx1)
      Plot3D_horizontalinterp(p3,tx1,tx3,ty1,tz1,tz3,c1,c3);
    else
      Plot3D_horizontalinterp(p3,tx3,tx1,ty1,tz3,tz1,c3,c1);
    if (tx3 > tx2)
      Plot3D_horizontalinterp(p3,tx2,tx3,ty2,tz2,tz3,c2,c3);
    else
      Plot3D_horizontalinterp(p3,tx3,tx2,ty2,tz3,tz2,c3,c2);
    
  } else {

    /* First process line segments between (x1,y1)-(x2,y2)
       And between (x1,y1),(x3,y3) */

    if (ty2 < ty1) {
      m1 = (double) (tx2 - tx1)/(double) (ty2 - ty1);
      m2 = (double) (tx3 - tx1)/(double) (ty3 - ty1);
      mz1 = (tz2 - tz1)/(double) (ty2 - ty1);
      mz2 = (tz3 - tz1)/(double) (ty3 - ty1);
      mc1 = (c2 - c1)/(double) (ty2 - ty1);
      mc2 = (c3 - c1)/(double) (ty3 - ty1);

      y = ty1;
      while (y >= ty2) {
	/* Calculate x values from slope */
	ix1 = (int) (m1*(y-ty1)+0.5) + tx1;
	ix2 = (int) (m2*(y-ty1)+0.5) + tx1;
	zz1 = mz1*(y-ty1) + tz1;
	zz2 = mz2*(y-ty1) + tz1;
	ic1 = mc1*(y-ty1) + c1;
	ic2 = mc2*(y-ty1) + c1;
	if (ix1 > ix2) 
	  Plot3D_horizontalinterp(p3,ix2,ix1,y,zz2,zz1,ic2,ic1);
	else
	  Plot3D_horizontalinterp(p3,ix1,ix2,y,zz1,zz2,ic1,ic2);
	y--;
      }
    }
    if (ty3 < ty2) {
      /* Draw lower half of the triangle */
      m2 = (double) (tx3 - tx1)/(double) (ty3 - ty1);
      mz2 = (tz3 - tz1)/(double) (ty3 - ty1);
      mc2 = (c3 - c1)/(double) (ty3 - ty1);
      m3 = (double) (tx3 - tx2)/(double)(ty3 - ty2);
      mz3 = (tz3 - tz2)/(double) (ty3 - ty2);
      mc3 = (c3 - c2)/(double) (ty3 - ty2);
      y = ty2;
      while (y >= ty3) {
	ix1 = (int) (m3*(y-ty2)+0.5)+tx2;
	ix2 = (int) (m2*(y-ty1)+0.5)+tx1;
	zz1 = mz3*(y-ty2)+tz2;
	zz2 = mz2*(y-ty1)+tz1;
	ic1 = mc3*(y-ty2)+c2;
	ic2 = mc2*(y-ty1)+c1;
	if (ix1 > ix2)
	  Plot3D_horizontalinterp(p3,ix2,ix1,y,zz2,zz1,ic2,ic1);
	else
	  Plot3D_horizontalinterp(p3,ix1,ix2,y,zz1,zz2,ic1,ic2);
	y--;
      }
    }
  }
}

/* -------------------------------------------------------------------------
   Plot3D_interptriangle(Plot3D *p3,
                         double x1, double y1, double z1, Pixel c1,
			 double x2, double y2, double z2, Pixel c2,
			 double x3, double y3, double z3, Pixel c3)

   This function draws a 3D z-buffered filled triangle with color
   interpolation.

   This function simply transforms the given points and calls
   Plot3D_InterpTriangleTransform().
   -------------------------------------------------------------------------- */

void Plot3D_interptriangle(Plot3D *p3,
			   double x1, double y1, double z1, Pixel c1,
			   double x2, double y2, double z2, Pixel c2,
			   double x3, double y3, double z3, Pixel c3) {

  int        tx1, tx2, tx3, ty1, ty2, ty3;
  Zvalue     tz1, tz2, tz3;
  GL_Vector  t;
  double     invw;
  
  /* Transform the three points into screen coordinates */

  Matrix_transform4(p3->trans_mat,x1,y1,z1,1,&t);  /* Point 1 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx1 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty1 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz1 = (Zvalue) t.z;
  
  Matrix_transform4(p3->trans_mat,x2,y2,z2,1,&t);  /* Point 2 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx2 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty2 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz2 = (Zvalue) t.z;

  Matrix_transform4(p3->trans_mat,x3,y3,z3,1,&t);  /* Point 3 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx3 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty3 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz3 = (Zvalue) t.z;

  Plot3D_interptriangletransform(p3,tx1,ty1,tz1,c1,tx2,ty2,tz2,c2,tx3,ty3,tz3,c3);
}    

/* -------------------------------------------------------------------------
   Plot3D_quad(Plot3D *p3, double x1, double y1, double z1,
                           double x2, double y2, double z2,
    		           double x3, double y3, double z3,
			   double x4, double y4, double z4,
 		           Pixel fillcolor)

   This function draws a 3D outlined Quadralateral.  Used primarily for
   drawing meshes and other things.

   Plotting is done in the following order :
       (x1,y1,z1)  --> (x2,y2,z2)
       (x2,y2,z2)  --> (x3,y3,z3)
       (x3,y3,z3)  --> (x4,y4,z4)
       (x4,y4,z4)  --> (x1,y1,z1)
   -------------------------------------------------------------------------- */

void Plot3D_quad(Plot3D *p3, double x1, double y1, double z1,
		 double x2, double y2, double z2,
		 double x3, double y3, double z3,
		 double x4, double y4, double z4,
		 Pixel color) {

  int        tx1, tx2, tx3, tx4, ty1, ty2, ty3, ty4;
  Zvalue     tz1, tz2, tz3, tz4;
  GL_Vector  t;
  double     invw;
  
  /* Transform the three points into screen coordinates */

  Matrix_transform4(p3->trans_mat,x1,y1,z1,1,&t);  /* Point 1 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx1 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty1 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz1 = (Zvalue) t.z;
  
  Matrix_transform4(p3->trans_mat,x2,y2,z2,1,&t);  /* Point 2 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx2 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty2 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz2 = (Zvalue) t.z;

  Matrix_transform4(p3->trans_mat,x3,y3,z3,1,&t);  /* Point 3 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx3 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty3 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz3 = (Zvalue) t.z;

  Matrix_transform4(p3->trans_mat,x4,y4,z4,1,&t);  /* Point 3 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx4 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty4 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz4 = (Zvalue) t.z;

  Plot3D_linetransform(p3,tx1,ty1,tz1,tx2,ty2,tz2,color);
  Plot3D_linetransform(p3,tx2,ty2,tz2,tx3,ty3,tz3,color);
  Plot3D_linetransform(p3,tx3,ty3,tz3,tx4,ty4,tz4,color);
  Plot3D_linetransform(p3,tx4,ty4,tz4,tx1,ty1,tz1,color);
  
}


/* -------------------------------------------------------------------------
   Plot3D_solidquad(Plot3D *p3, double x1, double y1, double z1,
                           double x2, double y2, double z2,
    		           double x3, double y3, double z3,
			   double x4, double y4, double z4,
 		           Pixel fillcolor)

   This function draws a 3D solid Quadralateral. Uses the function
   Plot3D_SolidTriangleTransform() to fill in the region.

   Plotting is done in the following order :
       (x1,y1,z1)  --> (x2,y2,z2)
       (x2,y2,z2)  --> (x3,y3,z3)
       (x3,y3,z3)  --> (x4,y4,z4)
       (x4,y4,z4)  --> (x1,y1,z1)
   -------------------------------------------------------------------------- */

void Plot3D_solidquad(Plot3D *p3, double x1, double y1, double z1,
		 double x2, double y2, double z2,
		 double x3, double y3, double z3,
		 double x4, double y4, double z4,
		 Pixel color) {

  int        tx1, tx2, tx3, tx4, ty1, ty2, ty3, ty4;
  Zvalue     tz1, tz2, tz3, tz4;
  GL_Vector  t;
  double     invw;
  
  /* Transform the three points into screen coordinates */

  Matrix_transform4(p3->trans_mat,x1,y1,z1,1,&t);  /* Point 1 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx1 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty1 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz1 = (Zvalue) t.z;
  
  Matrix_transform4(p3->trans_mat,x2,y2,z2,1,&t);  /* Point 2 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx2 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty2 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz2 = (Zvalue) t.z;

  Matrix_transform4(p3->trans_mat,x3,y3,z3,1,&t);  /* Point 3 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx3 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty3 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz3 = (Zvalue) t.z;

  Matrix_transform4(p3->trans_mat,x4,y4,z4,1,&t);  /* Point 3 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx4 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty4 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz4 = (Zvalue) t.z;

  Plot3D_solidtriangletransform(p3,tx1,ty1,tz1,tx2,ty2,tz2,tx3,ty3,tz3,color);
  Plot3D_solidtriangletransform(p3,tx1,ty1,tz1,tx4,ty4,tz4,tx3,ty3,tz3,color);
}

/* -------------------------------------------------------------------------
   Plot3D_interpquad(Plot3D *p3, double x1, double y1, double z1, Pixel c1,
                           double x2, double y2, double z2, Pixel c2,
    		           double x3, double y3, double z3, Pixel c3,
			   double x4, double y4, double z4, Pixel c4)

   This function draws a 3D color-interpolated Quadralateral. Uses the function
   Plot3D_InterpTriangleTransform() to fill in the region.

   Plotting is done in the following order :
       (x1,y1,z1)  --> (x2,y2,z2)
       (x2,y2,z2)  --> (x3,y3,z3)
       (x3,y3,z3)  --> (x4,y4,z4)
       (x4,y4,z4)  --> (x1,y1,z1)
   -------------------------------------------------------------------------- */

void Plot3D_interpquad(Plot3D *p3, double x1, double y1, double z1, Pixel c1,
		       double x2, double y2, double z2, Pixel c2,
		       double x3, double y3, double z3, Pixel c3,
		       double x4, double y4, double z4, Pixel c4) {


  int        tx1, tx2, tx3, tx4, ty1, ty2, ty3, ty4;
  Zvalue     tz1, tz2, tz3, tz4;
  GL_Vector  t;
  double     invw;
  
  /* Transform the three points into screen coordinates */

  Matrix_transform4(p3->trans_mat,x1,y1,z1,1,&t);  /* Point 1 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx1 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty1 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz1 = (Zvalue) t.z;
  
  Matrix_transform4(p3->trans_mat,x2,y2,z2,1,&t);  /* Point 2 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx2 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty2 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz2 = (Zvalue) t.z;

  Matrix_transform4(p3->trans_mat,x3,y3,z3,1,&t);  /* Point 3 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx3 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty3 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz3 = (Zvalue) t.z;

  Matrix_transform4(p3->trans_mat,x4,y4,z4,1,&t);  /* Point 3 */
  invw = 1.0/t.w;
  t.x = t.x *invw;
  t.y = t.y *invw;
  t.z = t.z *invw;
  tx4 = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty4 = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz4 = (Zvalue) t.z;

  Plot3D_interptriangletransform(p3,tx1,ty1,tz1,c1,tx2,ty2,tz2,c2,tx3,ty3,tz3,c3);
  Plot3D_interptriangletransform(p3,tx1,ty1,tz1,c1,tx4,ty4,tz4,c4,tx3,ty3,tz3,c3);
  
}

/* --------------------------------------------------------------------------
   Plot3D_solidsphere(Plot3 *p3, double x, double y, double z, double radius,
                      Pixel c)

   Makes a 3D sphere at x,y,z with given radius and color. 

   Basic strategy :
      1.  Transform point to screen coordinates
      2.  Figure out what the radius is in screen coordinates
      3.  Use bresenham algorithm for large spheres
      4.  Use bitmaps for small spheres
   -------------------------------------------------------------------------- */

/* This is used to fill in spheres */
static int s_xmin;
static int s_ymin;
static int s_xmax;
static int s_ymax;
static Pixel **s_pixels;
static Zvalue **s_zbuffer;

void Plot3D_spherehorizontal(int xmin, int xmax, int y, Zvalue z, Pixel color) {
  int     i;
  int     startx, endx;
  Pixel   *p;
  Zvalue  *zbuf;
  
  if ((y < s_ymin) || (y >= s_ymax)) return;
  if (xmin < s_xmin) startx = s_xmin;
  else startx = xmin;
  if (xmax >= s_xmax) endx = s_xmax - 1;
  else endx = xmax;

  /* Draw it */
  
  p = &s_pixels[y][xmin];
  zbuf = &s_zbuffer[y][xmin];
  for (i = startx; i <= endx; i++, p++, zbuf++) {
    if (z <= *zbuf) {
      *p = color;
      *zbuf = z;
    }
  }
}

void Plot3D_solidsphere(Plot3D *p3, double x, double y, double z, double radius,
			Pixel c) {

  GL_Vector  t,r;
  double     rad;
  int        tx,ty, irad;
  Zvalue     tz;
  double     invw;
  int        ix, iy, ix1,ix2,p;
  FrameBuffer *f;

  /* First transform the point into model coordinates */

  Matrix_transform4(p3->fullmodel_mat,x,y,z,1,&t);  

  /* Now transform two points in order to find proper sphere radius */

  Matrix_transform4(p3->view_mat,t.x+radius,t.y,t.z,t.w,&r);  /* transform radius */
  Matrix_transform4(p3->view_mat,t.x,t.y,t.z,t.w,&t);

  invw = 1.0/t.w;
  t.x = t.x*invw;
  t.y = t.y*invw;
  t.z = t.z*invw;
  invw = 1.0/r.w;
  r.x = r.x*invw;
  r.y = r.y*invw;
  r.z = r.z*invw;
  invw = 1.0/r.w;
  
  rad = fabs(t.x - r.x);
  
  /* Transform everything into screen coordinates */
  
  tx = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz = (Zvalue) t.z;
  irad = (int) (p3->zoom*(rad*p3->width + 0.5));

  /* This is only a temporary solution (maybe). */
         
#define fill_zcircle(x,y,c) \
  ix1 = tx - x; \
  ix2 = tx + x; \
  if (ix1 < s_xmin) ix1 = s_xmin; \
  if (ix2 >= s_xmax) ix2 = s_xmax; \
  Plot3D_spherehorizontal(ix1,ix2,y,tz,c);
  
  f = p3->frame;
  s_xmin = f->xmin;
  s_ymin = f->ymin;
  s_xmax = f->xmax;
  s_ymax = f->ymax;
  s_pixels = f->pixels;
  s_zbuffer = f->zbuffer;
  if (irad <= 1) {
    /* Plot a single pixel */
    if ((tx >= f->xmin) && (tx < f->xmax)) {
      if ((ty >= f->ymin) && (ty <f->ymax)) {
        if (tz <= f->zbuffer[ty][tx]) {
	  f->pixels[ty][tx] = c;
	  f->zbuffer[ty][tx] = tz;
	}
      }
    }
    return;
  }
  ix = 0;
  iy = irad;
  p = 3-2*irad;
  while (ix <= iy) {
    fill_zcircle(ix,ty+iy,c);
    fill_zcircle(ix,ty-iy,c);
    fill_zcircle(iy,ty+ix,c);
    fill_zcircle(iy,ty-ix,c);
    if (p < 0) p = p + 4*ix + 6;
    else {
      p = p + 4*(ix-iy) + 10;
      iy = iy -1;
    }
    ix++;
  }
}


/* --------------------------------------------------------------------
   Plot3D_outlinesphere(Plot3D *p3, double x, double y, double z,
                        double radius, Pixel color, Pixel bc)

   Draws an outlined sphere.
   -------------------------------------------------------------------- */

void Plot3D_outlinesphere(Plot3D *p3, double x, double y, double z,
			  double radius, Pixel c, Pixel bc)
{
  GL_Vector  t,r;
  double     rad;
  int        tx,ty, irad;
  Zvalue     tz;
  double     invw;
  int        ix, iy, ix1,ix2,p;
  
  FrameBuffer *f;

  /* First transform the point into model coordinates */

  Matrix_transform4(p3->fullmodel_mat,x,y,z,1,&t);  

  /* Now transform two points in order to find proper sphere radius */

  Matrix_transform4(p3->view_mat,t.x+radius,t.y,t.z,t.w,&r);  /* transform radius */
  Matrix_transform4(p3->view_mat,t.x,t.y,t.z,t.w,&t);

  invw = 1.0/t.w;
  t.x = t.x*invw;
  t.y = t.y*invw;
  t.z = t.z*invw;
  invw = 1.0/r.w;
  r.x = r.x*invw;
  r.y = r.y*invw;
  r.z = r.z*invw;
  invw = 1.0/r.w;
  
  rad = fabs(t.x - r.x);
  
  /* Transform everything into screen coordinates */
  
  tx = (int) ((t.x +p3->xshift)*p3->zoom*p3->width + 0.5) + p3->view_xmin; 
  ty = (int) ((t.y +p3->yshift)*p3->zoom*p3->height + 0.5) + p3->view_ymin;
  tz = (Zvalue) t.z;
  irad = (int) (p3->zoom*(rad*p3->width + 0.5));

  /* This is only a temporary solution (maybe). */
#define plot_zcircle(x,y,c) \
   if ((x >= s_xmin) && (x < s_xmax) && \
       (y >= s_ymin) && (y < s_ymax)) {\
         if (tz <= s_zbuffer[y][x]) { \
             s_pixels[y][x] = c; \
	     s_zbuffer[y][x] = tz; } \
   }
     
  f = p3->frame;
  s_xmin = f->xmin;
  s_ymin = f->ymin;
  s_xmax = f->xmax;
  s_ymax = f->ymax;
  s_pixels = f->pixels;
  s_zbuffer = f->zbuffer;
  
  if (irad <= 1) {
    /* Plot a single pixel */
    if ((tx >= f->xmin) && (tx < f->xmax)) {
      if ((ty >= f->ymin) && (ty <f->ymax)) {
        if (tz <= f->zbuffer[ty][tx]) {
	  f->pixels[ty][tx] = c;
	  f->zbuffer[ty][tx] = tz;
	}
      }
    }
    return;
  }
  ix = 0;
  iy = irad;
  p = 3-2*irad;
  while (ix <= iy) {
    fill_zcircle(ix,ty+iy,c);
    fill_zcircle(ix,ty-iy,c);
    fill_zcircle(iy,ty+ix,c);
    fill_zcircle(iy,ty-ix,c);
    
    plot_zcircle(tx+ix,ty+iy,bc);
    plot_zcircle(tx-ix,ty+iy,bc);
    plot_zcircle(tx+ix,ty-iy,bc);
    plot_zcircle(tx-ix,ty-iy,bc);
    plot_zcircle(tx+iy,ty+ix,bc);
    plot_zcircle(tx-iy,ty+ix,bc);
    plot_zcircle(tx+iy,ty-ix,bc);
    plot_zcircle(tx-iy,ty-ix,bc);
    if (p < 0) p = p + 4*ix + 6;
    else {
      p = p + 4*(ix-iy) + 10;
      iy = iy -1;
    }
    ix++;
  }
}

/* QUAD Test
   Test out quad functions for graphing */

double zf(double x, double y) {
  return cos(sqrt(x*x + y*y)*10.0)/(sqrt(x*x+y*y)+1);
}

void Quad_Test(Plot3D *p3, int npoints) {
  int i,j;
  double dx;
  double x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4,za;
  int c;
  dx = 2.0/npoints;

  
  for (i = 0; i < npoints; i++)
    for (j = 0; j < npoints; j++) {
      x1 = i*dx + -1.0;
      y1 = j*dx + -1.0;
      x2 = x1 + dx;
      x3 = x1 + dx;
      x4 = x1;
      y2 = y1;
      y3 = y1 + dx;
      y4 = y1 + dx;
      z1 = zf(x1,y1);
      z2 = zf(x2,y2);
      z3 = zf(x3,y3);
      z4 = zf(x4,y4);
      za = 0.25*(z1+z2+z3+z4);
      c = 16+((za + 1)*120);
      if (c > 254) c = 254;
      Plot3D_quad(p3,x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4,(Pixel) c);
    }
}


void Quad_SolidTest(Plot3D *p3, int npoints) {
  int i,j;
  double dx;
  double x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4,za;
  int c;
  dx = 2.0/npoints;

  
  for (i = 0; i < npoints; i++)
    for (j = 0; j < npoints; j++) {
      x1 = i*dx + -1.0;
      y1 = j*dx + -1.0;
      x2 = x1 + dx;
      x3 = x1 + dx;
      x4 = x1;
      y2 = y1;
      y3 = y1 + dx;
      y4 = y1 + dx;
      z1 = zf(x1,y1);
      z2 = zf(x2,y2);
      z3 = zf(x3,y3);
      z4 = zf(x4,y4);
      za = 0.25*(z1+z2+z3+z4);
      c = 16+((za + 1)*120);
      if (c > 254) c = 254;
      Plot3D_solidquad(p3,x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4,(Pixel) c);
    }
}



void Quad_InterpTest(Plot3D *p3, int npoints) {
  int i,j;
  double dx;
  double x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4;
  int c1,c2,c3,c4;
  dx = 2.0/npoints;

  
  for (i = 0; i < npoints; i++)
    for (j = 0; j < npoints; j++) {
      x1 = i*dx + -1.0;
      y1 = j*dx + -1.0;
      x2 = x1 + dx;
      x3 = x1 + dx;
      x4 = x1;
      y2 = y1;
      y3 = y1 + dx;
      y4 = y1 + dx;
      z1 = zf(x1,y1);
      z2 = zf(x2,y2);
      z3 = zf(x3,y3);
      z4 = zf(x4,y4);
      c1 = 16+((z1 + 1)*120);
      c2 = 16+((z2 + 1)*120);
      c3 = 16+((z3 + 1)*120);
      c4 = 16+((z4 + 1)*120);
      if (c1 > 254) c1 = 254;
      if (c2 > 254) c2 = 254;
      if (c3 > 254) c3 = 254;
      if (c4 > 254) c4 = 254;
      Plot3D_interpquad(p3,x1,y1,z1,(Pixel) c1,x2,y2,z2,(Pixel) c2,x3,y3,z3,(Pixel) c3,x4,y4,z4,(Pixel) c4);
    }
}



  
  
  
   


  
  
  
