#include <owl\owlpch.h>
#include <owl\applicat.h>
#include <owl\framewin.h>
#include <owl\dc.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include "plot3d.h"

#ifndef TRUE
#define TRUE -1
#endif
#ifndef FALSE
#define FALSE 0
#endif

plot3d::plot3d(TFrameWindow *ptr)
  {
    window_ptr=ptr;
    TClientDC *dc=new TClientDC(*window_ptr);
    long num_colors_free=1L
     << (long(dc->GetDeviceCaps(PLANES))*long(dc->GetDeviceCaps(BITSPIXEL)));
    num_colors_free-=long(dc->GetDeviceCaps(NUMCOLORS));
    delete dc;
    if (num_colors_free < 16L)
      {
        use_palette=FALSE;
        num_colors=256;
      }
    else
      {
        use_palette=TRUE;
        if (num_colors_free > 256L)
          num_colors=256;
        else
          num_colors=int(num_colors_free);
      }
    corner_allocated=FALSE;
    plot_prepared=FALSE;
    prepare_plot_state='B';
    plot_state='B';
    int color_num;
    for (int color_index=0; color_index < (num_colors-1); color_index++)
      {
        color_num=int((255L*long(color_index))/long(num_colors-2));
        palette_entry[color_index].peRed=(BYTE) color_num;
        palette_entry[color_index].peGreen=(BYTE) color_num;
        palette_entry[color_index].peBlue=(BYTE) color_num;
        palette_entry[color_index].peFlags=(BYTE) PC_NOCOLLAPSE;
      }
    palette_entry[color_index].peRed=(BYTE) 255;
    palette_entry[color_index].peGreen=(BYTE) 0;
    palette_entry[color_index].peBlue=(BYTE) 0;
    palette_entry[color_index].peFlags=(BYTE) PC_NOCOLLAPSE;
    palette=new TPalette(&palette_entry[0],num_colors);
    lightest_gray=int((240L*long(num_colors-2))/254L);
    darkest_gray=(19*(num_colors-2))/254;
  }

plot3d::~plot3d()
  {
    delete palette;
    if (corner_allocated)
      {
        for (x_division_num=(num_x_divisions-1); x_division_num >= 0;
         x_division_num--)
          delete[] corner[x_division_num--];
        delete[] corner;
      }
  }

char plot3d::prepare_plot(
  double (*f)(double,double),
  double x_min,
  double x_max,
  double y_min,
  double y_max,
  int    (*external_to_plot)(double,double),
  int    (*red)(double,double),
  int    x_division_count,
  int    y_division_count,
  double rotation_in_degrees,
  double tilt_in_degrees,
  double light_x,
  double light_y,
  double light_z)
//     This function returns 'S' if it is successful in preparing the plot for
//  generation.  It returns 'C' if it should be called again (because more work
//  must be done).  Otherwise, it returns 'F' indicating a failure.  Its
//  parameters are as follow:
//
//           f -- z=f(x,y), the function to be plotted.  Before the plot is
//      tilted or rotated, the z-axis runs from the bottom to the top of the
//      display, the y-axis runs from the left to the right of the display,
//      and the x-axis runs out of the display.
//
//           x_min -- the minimum value of x to be plotted.
//
//           x_max -- the maximum value of x to be plotted.
//
//           y_min -- the minimum value of y to be plotted.
//
//           y_max -- the maximum value of y to be plotted.
//
//           external_to_plot -- a function that returns TRUE if and only if a
//      point should be omitted from the plot.
//
//           red -- a function that returns TRUE if and only if a point should
//      be flagged for highlighting.  A point should be so flagged only if it
//      can be seen in the final plot.
//
//           x_division_count -- the number of x divisions to be used in
//      constructing the plot.  At least two must be specified.
//
//           y_division_count -- the number of y divisions to be used in
//      constructing the plot.  At least two must be specified.
//
//           rotation_in_degrees -- rotation (degrees) about an axis parallel to
//      the z-axis and through the center of the surface.
//
//           tilt_in_degrees -- tilt (degrees) about an axis through the center
//      of the surface and parallel to a line from the lower left hand corner of
//      the display to the lower right hand corner of the display.  The plot is
//      tilted after it is rotated.
//
//           (light_x,light_y,light_z) -- a vector pointing to the light source
//      (at infinity).  The light source remains fixed while the plot is rotated
//      or tilted.
    {
      char result;

      result='C';
      switch (prepare_plot_state)
        {
           case 'B': // begin
             plot_prepared=FALSE;
             if (corner_allocated)
               {
                 for (x_division_num=(num_x_divisions-1); x_division_num >= 0;
                  x_division_num--)
                   delete[] corner[x_division_num--];
                 delete[] corner;
                 corner_allocated=FALSE;
               }
             num_x_divisions=x_division_count;
             num_y_divisions=y_division_count;
             corner_allocated
              =((corner=new corner_rec_ptr [num_x_divisions]) != NULL);
             for (x_division_num=0;
              ((corner_allocated) && (x_division_num < num_x_divisions));
              x_division_num++)
               if (corner_allocated=
                ((corner [x_division_num]=new corner_rec [num_y_divisions])
                != NULL))
                 for (y_division_num=0; y_division_num < num_y_divisions;
                  y_division_num++)
                   {
                     corner[x_division_num][y_division_num].base_z
                      =(unsigned char) '\0';
                     corner[x_division_num][y_division_num].color
                      =(unsigned char) '\0';
                     corner[x_division_num][y_division_num].x=(float) 0.0;
                     corner[x_division_num][y_division_num].y=(float) 0.0;
                     corner[x_division_num][y_division_num].z=(float) 0.0;
                     corner[x_division_num][y_division_num].x_division_index=0;
                     corner[x_division_num][y_division_num].y_division_index=0;
                   };
             if (corner_allocated)
               {
                 rotation=rotation_in_degrees;
                 tilt=tilt_in_degrees;
                 light.x=light_x;
                 light.y=light_y;
                 light.z=light_z;
                 prepare_plot_state='E';
                 TClientDC *dc=new TClientDC(*window_ptr);
                 dc->PatBlt(window_ptr->GetClientRect(),WHITENESS);
                 dc->TextOut(0,0,"Evaluating...");
                 delete dc;
                 x_division_num=0;
                 y_division_num=0;
               }
             else
               {
                 x_division_num--;
                 while (x_division_num >= 0)
                   delete[] corner[x_division_num--];
                 delete[] corner;
                 prepare_plot_state='F';
                 result='F';
               }
             break;
           case 'E': // evaluate and transform
             evaluate_and_transform(f,x_min,x_max,y_min,y_max,
              num_x_divisions,num_y_divisions,rotation,tilt,
              external_to_plot,red);
              // Compute the vertices, etc. of the quadrilaterals composing the
              // plot.
             if (x_division_num >= num_x_divisions)
               {
                 prepare_plot_state='H';
                 TClientDC *dc=new TClientDC(*window_ptr);
                 dc->PatBlt(window_ptr->GetClientRect(),WHITENESS);
                 dc->TextOut(0,0,"Shading...");
                 delete dc;
                 x_division_num=num_x_divisions-1;
                 y_division_num=num_y_divisions-1;
               }
             break;
           case 'H': // shade
             shade();
               // Compute the shade of gray for each quadrilateral composing the
               // plot.
             if (x_division_num < 0)
               {
                 if (((y_corner_max-y_corner_min) > (z_corner_max-z_corner_min))
                 ||  (z_corner_max != z_corner_min))
                   {
                     if ((y_corner_max-y_corner_min)
                      > (z_corner_max-z_corner_min))
                       x_eye=1.1*(y_corner_max-y_corner_min)+x_corner_max;
                     else
                       x_eye=1.1*(z_corner_max-z_corner_min)+x_corner_max;
                     prepare_plot_state='A';
                     TClientDC *dc=new TClientDC(*window_ptr);
                     dc->PatBlt(window_ptr->GetClientRect(),WHITENESS);
                     dc->TextOut(0,0,"Adjusting perspective...");
                     delete dc;
                     x_division_num=0;
                     y_division_num=0;
                   }
                 else
                   {
                     stacked_bound_head=new sort_stack_rec;
                     stacked_bound_head->bound.lower_x=0;
                     stacked_bound_head->bound.lower_y=0;
                     stacked_bound_head->bound.upper_x=(num_x_divisions-1);
                     stacked_bound_head->bound.upper_y=(num_y_divisions-1);
                     stacked_bound_head->previous=NULL;
                     prepare_plot_state='S';
                     TClientDC *dc=new TClientDC(*window_ptr);
                     dc->PatBlt(window_ptr->GetClientRect(),WHITENESS);
                     dc->TextOut(0,0,"Sorting...");
                     delete dc;
                   }
               }
             break;
           case 'A': // adjust perspective
             adjust_perspective();
               // Force parallel lines running away from the viewer to converge
               // at the horizon.
             if (x_division_num >= num_x_divisions)
               {
                 stacked_bound_head=new sort_stack_rec;
                 stacked_bound_head->bound.lower_x=0;
                 stacked_bound_head->bound.lower_y=0;
                 stacked_bound_head->bound.upper_x=(num_x_divisions-1);
                 stacked_bound_head->bound.upper_y=(num_y_divisions-1);
                 stacked_bound_head->previous=NULL;
                 prepare_plot_state='S';
                 TClientDC *dc=new TClientDC(*window_ptr);
                 dc->PatBlt(window_ptr->GetClientRect(),WHITENESS);
                 dc->TextOut(0,0,"Sorting...");
                 delete dc;
               }
             break;
           case 'S': // sort back to front
             sort_back_to_front();
             if (stacked_bound_head == NULL)
               {
                 prepare_plot_state='D';
                 TClientDC *dc=new TClientDC(*window_ptr);
                 dc->PatBlt(window_ptr->GetClientRect(),WHITENESS);
                 delete dc;
               }
             break;
           case 'F': // failed
             result='F';
             break;
           default: // done
             plot_prepared=TRUE;
             result='S';
             break;
        }
      return result;
    }

void plot3d::evaluate_and_transform(
  double (*f)(double,double),
  double x_min,
  double x_max,
  double y_min,
  double y_max,
  int    num_x_divisions,
  int    num_y_divisions,
  double rotation,
  double tilt,
  int    (*external_to_plot)(double,double),
  int    (*red)(double,double))
// Compute the vertices, etc. for each quadrilateral composing the plot.
    {
      double tem_x;
      double tem_y;
      double tem_z;
      double x_rotated;
      double z;

      if ((x_division_num == 0) && (y_division_num == 0))
        {
          double degrees_per_radian=45.0/atan(1.0);
          radians=tilt/degrees_per_radian;
          cos_tilt=cos(radians);
          sin_tilt=sin(radians);
          radians=rotation/degrees_per_radian;
          cos_rotation=cos(radians);
          sin_rotation=sin(radians);
          z=f(x_min,y_min);
          x_rotated=x_min*cos_rotation+y_min*sin_rotation;
          y_corner_min=-x_min*sin_rotation+y_min*cos_rotation;
          z_corner_min=-x_rotated*sin_tilt+z*cos_tilt;
          y_corner_max=y_corner_min;
          z_corner_max=z_corner_min;
          x_corner_max=x_rotated*cos_tilt+z*sin_tilt;
          x_delta=(double) (num_x_divisions-1);
          x_delta=(x_max-x_min)/x_delta;
          y_delta=(double) (num_y_divisions-1);
          y_delta=(y_max-y_min)/y_delta;
          x=x_min;
          y=y_min;
        }
      int finished=FALSE;
      DWORD start_time=GetTickCount();
      DWORD current_time;
      while ((! finished) && (x_division_num < num_x_divisions))
        {
          z=f(x,y);
          if (external_to_plot(x,y))
            corner[x_division_num][y_division_num].base_z=(unsigned char) 3;
          else
            if (red(x,y))
              corner[x_division_num][y_division_num].base_z=(unsigned char) 2;
            else
              corner[x_division_num][y_division_num].base_z=(unsigned char) 1;
          corner[x_division_num][y_division_num].x_division_index
           =x_division_num;
          corner[x_division_num][y_division_num].y_division_index
           =y_division_num;
          x_rotated=x*cos_rotation+y*sin_rotation;
          tem_y=(-x*sin_rotation+y*cos_rotation);
          corner[x_division_num][y_division_num].y=(float) tem_y;
          tem_x=(x_rotated*cos_tilt+z*sin_tilt);
          corner[x_division_num][y_division_num].x=(float) tem_x;
          tem_z=(-x_rotated*sin_tilt+z*cos_tilt);
          corner[x_division_num][y_division_num].z=(float) tem_z;
          if (tem_x > x_corner_max)
            x_corner_max=tem_x;
          if (tem_y < y_corner_min)
            y_corner_min=tem_y;
          if (tem_y > y_corner_max)
            y_corner_max=tem_y;
          if (tem_z < z_corner_min)
            z_corner_min=tem_z;
          if (tem_z > z_corner_max)
            z_corner_max=tem_z;
          y+=y_delta;
          y_division_num++;
          if (y_division_num >= num_y_divisions)
            {
              y_division_num=0;
              y=y_min;
              x+=x_delta;
              x_division_num++;
            }
          current_time=GetTickCount();
          finished=((current_time < start_time)
           || ((current_time-start_time) > 100));
        }
      if (x_division_num >= num_x_divisions)
        {
          double magnitude=light.x*light.x+light.y*light.y+light.z*light.z;
          magnitude=sqrt(magnitude);
          light.x=light.x/magnitude;
          light.y=light.y/magnitude;
          light.z=light.z/magnitude;
        }
      return;
    }

void plot3d::shade()
// Compute the shade of gray for each quadrilateral composing the plot.
    {
      int        color_num;
      double     magnitude;
      vertex_rec normal;
      vertex_rec vertex [4];

      if ((x_division_num == (num_x_divisions-1))
      &&  (y_division_num == (num_y_divisions-1)))
        {
          color_min=(unsigned char) (num_colors-1);
          color_max=(unsigned char) '\0';
        }
      int finished=FALSE;
      DWORD start_time=GetTickCount();
      DWORD current_time;
      while ((! finished) && (x_division_num >= 0))
        {
          vertex[0].x=(double) (corner[x_division_num][y_division_num].x);
          vertex[0].y=(double) (corner[x_division_num][y_division_num].y);
          vertex[0].z=(double) (corner[x_division_num][y_division_num].z);
          if (x_division_num < (num_x_divisions-1))
            if (y_division_num < (num_y_divisions-1))
              {
                x_division_num++;
                vertex[1].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[1].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[1].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num++;
                vertex[2].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[2].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[2].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num--;
                vertex[3].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[3].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[3].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num--;
              }
            else
              {
                y_division_num--;
                vertex[1].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[1].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[1].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num++;
                vertex[2].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[2].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[2].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num++;
                vertex[3].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[3].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[3].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num--;
              }
          else
            if (y_division_num < (num_y_divisions-1))
              {
                y_division_num++;
                vertex[1].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[1].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[1].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num--;
                vertex[2].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[2].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[2].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num--;
                vertex[3].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[3].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[3].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num++;
              }
            else
              {
                x_division_num--;
                vertex[1].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[1].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[1].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num--;
                vertex[2].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[2].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[2].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num++;
                vertex[3].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[3].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[3].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num++;
              }
          // Compute the normal to a quadrilateral by averaging the
          // normals to each vertex of the quadrilateral.
          normal.x
           =(vertex[1].y-vertex[0].y)*(vertex[3].z-vertex[0].z)
           -(vertex[3].y-vertex[0].y)*(vertex[1].z-vertex[0].z)
           +(vertex[2].y-vertex[1].y)*(vertex[0].z-vertex[1].z)
           -(vertex[0].y-vertex[1].y)*(vertex[2].z-vertex[1].z)
           +(vertex[3].y-vertex[2].y)*(vertex[1].z-vertex[2].z)
           -(vertex[1].y-vertex[2].y)*(vertex[3].z-vertex[2].z)
           +(vertex[0].y-vertex[3].y)*(vertex[2].z-vertex[3].z)
           -(vertex[2].y-vertex[3].y)*(vertex[0].z-vertex[3].z);
          normal.y
           =(vertex[3].x-vertex[0].x)*(vertex[1].z-vertex[0].z)
           -(vertex[1].x-vertex[0].x)*(vertex[3].z-vertex[0].z)
           +(vertex[0].x-vertex[1].x)*(vertex[2].z-vertex[1].z)
           -(vertex[2].x-vertex[1].x)*(vertex[0].z-vertex[1].z)
           +(vertex[1].x-vertex[2].x)*(vertex[3].z-vertex[2].z)
           -(vertex[3].x-vertex[2].x)*(vertex[1].z-vertex[2].z)
           +(vertex[2].x-vertex[3].x)*(vertex[0].z-vertex[3].z)
           -(vertex[0].x-vertex[3].x)*(vertex[2].z-vertex[3].z);
          normal.z
           =(vertex[1].x-vertex[0].x)*(vertex[3].y-vertex[0].y)
           -(vertex[3].x-vertex[0].x)*(vertex[1].y-vertex[0].y)
           +(vertex[2].x-vertex[1].x)*(vertex[0].y-vertex[1].y)
           -(vertex[0].x-vertex[1].x)*(vertex[2].y-vertex[1].y)
           +(vertex[3].x-vertex[2].x)*(vertex[1].y-vertex[2].y)
           -(vertex[1].x-vertex[2].x)*(vertex[3].y-vertex[2].y)
           +(vertex[0].x-vertex[3].x)*(vertex[2].y-vertex[3].y)
           -(vertex[2].x-vertex[3].x)*(vertex[0].y-vertex[3].y);
          magnitude
           =sqrt(normal.x*normal.x+normal.y*normal.y+normal.z*normal.z);
          if (magnitude == 0.0)
            {
              color_min=(unsigned char) '\0';
              corner[x_division_num][y_division_num].color=(unsigned char) '\0';
            }
          else
            {
              color_num=int((double(num_colors)/2.0)
               *(1.0+(light.x*normal.x+light.y*normal.y
               +light.z*normal.z)/magnitude)); // shadows not absolute
              if (color_num >= num_colors)
                color_num=num_colors-1;
              corner[x_division_num][y_division_num].color
               =(unsigned char) color_num;
              if ((corner[x_division_num][y_division_num].color) < color_min)
                color_min=(corner[x_division_num][y_division_num].color);
              if ((corner[x_division_num][y_division_num].color) > color_max)
                color_max=(corner[x_division_num][y_division_num].color);
            }
          y_division_num--;
          if (y_division_num < 0)
            {
              y_division_num=num_y_divisions-1;
              x_division_num--;
            }
          current_time=GetTickCount();
          finished=((current_time < start_time)
           || ((current_time-start_time) > 100));
        }
      return;
    }

void plot3d::adjust_perspective()
// Make parallel lines running away from the viewer appear to converge at the
// horizon.  
    {
      double     tem;
      vertex_rec vertex [4];

      if ((x_division_num == 0) && (y_division_num == 0))
        {
          y_center=(y_corner_max+y_corner_min)/2.0;
          z_center=(z_corner_max+z_corner_min)/2.0;
        }
      int finished=FALSE;
      DWORD start_time=GetTickCount();
      DWORD current_time;
      while ((! finished) && (x_division_num < num_x_divisions))
        {
          vertex[0].x=(double) (corner[x_division_num][y_division_num].x);
          vertex[0].y=(double) (corner[x_division_num][y_division_num].y);
          vertex[0].z=(double) (corner[x_division_num][y_division_num].z);
          if (x_division_num < (num_x_divisions-1))
            if (y_division_num < (num_y_divisions-1))
              {
                x_division_num++;
                vertex[1].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[1].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[1].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num++;
                vertex[2].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[2].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[2].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num--;
                vertex[3].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[3].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[3].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num--;
              }
            else
              {
                y_division_num--;
                vertex[1].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[1].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[1].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num++;
                vertex[2].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[2].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[2].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num++;
                vertex[3].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[3].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[3].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num--;
              }
          else
            if (y_division_num < (num_y_divisions-1))
              {
                y_division_num++;
                vertex[1].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[1].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[1].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num--;
                vertex[2].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[2].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[2].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num--;
                vertex[3].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[3].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[3].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num++;
              }
            else
              {
                x_division_num--;
                vertex[1].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[1].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[1].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num--;
                vertex[2].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[2].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[2].z=(double) (corner[x_division_num][y_division_num].z);
                x_division_num++;
                vertex[3].x=(double) (corner[x_division_num][y_division_num].x);
                vertex[3].y=(double) (corner[x_division_num][y_division_num].y);
                vertex[3].z=(double) (corner[x_division_num][y_division_num].z);
                y_division_num++;
              }
          tem=y_center
           +(vertex[0].y-y_center)*(x_eye-x_corner_max)
           /(x_eye-vertex[0].x);
          corner[x_division_num][y_division_num].y=(float) tem;
          tem=z_center
           +(vertex[0].z-z_center)*(x_eye-x_corner_max)
           /(x_eye-vertex[0].x);
          corner[x_division_num][y_division_num].z=(float) tem;
          tem=(vertex[0].x+vertex[1].x+vertex[2].x+vertex[3].x)/4.0;
          corner[x_division_num][y_division_num].x=(float) tem;
          y_division_num++;
          if (y_division_num >= num_y_divisions)
            {
              y_division_num=0;
              x_division_num++;
            }
          current_time=GetTickCount();
          finished=((current_time < start_time)
           || ((current_time-start_time) > 100));
        }
      return;
    }

void plot3d::rearrange(
  int lower_bound_x,
  int lower_bound_y,
  int upper_bound_x,
  int upper_bound_y,
  int *j_x,
  int *j_y)
    {
      int       down_x;
      int       down_y;
      int       finished;
      int       up_x;
      int       up_y;
      float     x1;
      int       x_division_index1;
      int       y_division_index1;

      x1=corner[lower_bound_x][lower_bound_y].x;
      x_division_index1=corner[lower_bound_x][lower_bound_y].x_division_index;
      y_division_index1=corner[lower_bound_x][lower_bound_y].y_division_index;
      *j_x=lower_bound_x;
      *j_y=lower_bound_y;
      up_x=upper_bound_x;
      up_y=upper_bound_y;
      down_x=lower_bound_x;
      down_y=lower_bound_y;
      do
        {
          finished=FALSE;
          while (! finished)
            {
              if ((up_x < down_x)
              ||  ((up_x == down_x) && (up_y <= down_y)))
                finished=TRUE;
              else
                if (corner[up_x][up_y].x < x1)
                  finished=TRUE;
                else
                  {
                    if (--up_y < 0)
                      {
                        up_y=num_y_divisions-1;
                        up_x--;
                      }
                  }
            }
          *j_x=up_x;
          *j_y=up_y;
          if ((up_x != down_x) || (up_y != down_y))
            {
              corner[down_x][down_y].x=corner[up_x][up_y].x;
              corner[down_x][down_y].x_division_index
               =corner[up_x][up_y].x_division_index;
              corner[down_x][down_y].y_division_index
               =corner[up_x][up_y].y_division_index;
              finished=FALSE;
              while (! finished)
                {
                  if ((down_x > up_x)
                  ||  ((down_x == up_x) && (down_y >= up_y)))
                    finished=TRUE;
                  else
                    if (corner[down_x][down_y].x > x1)
                      finished=TRUE;
                    else
                      {
                        if (++down_y >= num_y_divisions)
                          {
                            down_y=0;
                            down_x++;
                          }
                      }
                }
              *j_x=down_x;
              *j_y=down_y;
              if ((down_x != up_x) || (down_y != up_y))
                {
                  corner[up_x][up_y].x=corner[down_x][down_y].x;
                  corner[up_x][up_y].x_division_index
                   =corner[down_x][down_y].x_division_index;
                  corner[up_x][up_y].y_division_index
                   =corner[down_x][down_y].y_division_index;
                }
            }
        }
      while ((down_x != up_x) || (down_y != up_y));
      corner[*j_x][*j_y].x=x1;
      corner[*j_x][*j_y].x_division_index=x_division_index1;
      corner[*j_x][*j_y].y_division_index=y_division_index1;
      return;
    }

void plot3d::sort_back_to_front()
//      The painter's algorithm is used; items farther from the viewer are drawn
// earlier.
    {
      int            i_x;
      int            i_y;
      int            j_x;
      int            j_y;
      sort_bound_rec stacked_bound;
      sort_stack_rec *stacked_bound_ptr;

      int finished=FALSE;
      DWORD start_time=GetTickCount();
      DWORD current_time;
      while ((! finished) && (stacked_bound_head != NULL))
        {
          stacked_bound.lower_x=stacked_bound_head->bound.lower_x;
          stacked_bound.lower_y=stacked_bound_head->bound.lower_y;
          stacked_bound.upper_x=stacked_bound_head->bound.upper_x;
          stacked_bound.upper_y=stacked_bound_head->bound.upper_y;
          stacked_bound_ptr=stacked_bound_head;
          stacked_bound_head=stacked_bound_head->previous;
          delete stacked_bound_ptr;
          while ((stacked_bound.upper_x > stacked_bound.lower_x)
          ||     ((stacked_bound.upper_x == stacked_bound.lower_x)
               && (stacked_bound.upper_y > stacked_bound.lower_y)))
            {
              rearrange(stacked_bound.lower_x,stacked_bound.lower_y,
               stacked_bound.upper_x,stacked_bound.upper_y,&j_x,&j_y);
              if (long(num_y_divisions)*long(j_x-stacked_bound.lower_x)
               +(j_y-stacked_bound.lower_y)
               >  long(num_y_divisions)*long(stacked_bound.upper_x-j_x)
               +(stacked_bound.upper_y-j_y))
                {
                  i_x=stacked_bound.upper_x;
                  i_y=stacked_bound.upper_y;
                  stacked_bound.upper_y=j_y-1;
                  stacked_bound.upper_x=j_x;
                  if (stacked_bound.upper_y < 0)
                    {
                      stacked_bound.upper_y=num_y_divisions-1;
                      (stacked_bound.upper_x)--;
                    }
                  stacked_bound_ptr=new sort_stack_rec;
                  stacked_bound_ptr->bound.lower_x=stacked_bound.lower_x;
                  stacked_bound_ptr->bound.lower_y=stacked_bound.lower_y;
                  stacked_bound_ptr->bound.upper_x=stacked_bound.upper_x;
                  stacked_bound_ptr->bound.upper_y=stacked_bound.upper_y;
                  stacked_bound_ptr->previous=stacked_bound_head;
                  stacked_bound_head=stacked_bound_ptr;
                  stacked_bound.lower_y=j_y+1;
                  stacked_bound.lower_x=j_x;
                  if (stacked_bound.lower_y >= num_y_divisions)
                    {
                      stacked_bound.lower_y=0;
                      (stacked_bound.lower_x)++;
                    }
                  stacked_bound.upper_x=i_x;
                  stacked_bound.upper_y=i_y;
                }
              else
                {
                  i_x=stacked_bound.lower_x;
                  i_y=stacked_bound.lower_y;
                  stacked_bound.lower_y=j_y+1;
                  stacked_bound.lower_x=j_x;
                  if (stacked_bound.lower_y >= num_y_divisions)
                    {
                      stacked_bound.lower_y=0;
                      (stacked_bound.lower_x)++;
                    }
                  stacked_bound_ptr=new sort_stack_rec;
                  stacked_bound_ptr->bound.lower_x=stacked_bound.lower_x;
                  stacked_bound_ptr->bound.lower_y=stacked_bound.lower_y;
                  stacked_bound_ptr->bound.upper_x=stacked_bound.upper_x;
                  stacked_bound_ptr->bound.upper_y=stacked_bound.upper_y;
                  stacked_bound_ptr->previous=stacked_bound_head;
                  stacked_bound_head=stacked_bound_ptr;
                  stacked_bound.lower_x=i_x;
                  stacked_bound.lower_y=i_y;
                  stacked_bound.upper_y=j_y-1;
                  stacked_bound.upper_x=j_x;
                  if (stacked_bound.upper_y < 0)
                    {
                      stacked_bound.upper_y=num_y_divisions-1;
                      (stacked_bound.upper_x)--;
                    }
                }
            }
          current_time=GetTickCount();
          finished=((current_time < start_time)
           || ((current_time-start_time) > 100));
        }
      return;
    }

char plot3d::plot(
  TRect   &region_to_paint,
  int     show_red,
  int     only_plot_red,
  double  bias)
//     This function returns 'S' if it is successful in generating the 3D plot.
//  It returns 'C' if it should be called again (because more work must be done
//  on the plot).  Otherwise, it returns 'F' indicating a failure.  Its
//  parameters are as follow:
//
//           region_to_paint -- only points in this rectangle will be plotted.
//
//           show_red -- highlight quadrilaterals having each vertex flagged
//      to be highlighted.  
//
//           only_plot_red -- only plot the quadrilaterals having each vertex
//      flagged to be highlighted.
//
//           bias -- a positive number used to adjust the contrast.
//
// "prepare_plot" must be called before "plot", after which "plot" may be called
// as many times as desired.
    {
      TPoint     box [4];
      int        box_num;
      int        color_num;
      DWORD      current_time;
      int        finished;
      double     fraction;
      int        outside_maze;
      int        red_showing;
      char       result;
      DWORD      start_time;
      vertex_rec vertex [4];
      int        x_division_index;
      int        y_division_index;
      double     y_out_max;

      if (plot_prepared)
        {
          result='C';
          TClientDC *dc=new TClientDC(*window_ptr);
          TRect ClipBox;
          if (dc->GetClipBox(ClipBox) != NULLREGION)
            switch (plot_state)
              {
                case 'B': // begin
                  aspect_ratio
                   =(double(dc->GetDeviceCaps(VERTSIZE))
                   /double(dc->GetDeviceCaps(HORZSIZE)))
                   /(double(dc->GetDeviceCaps(VERTRES))
                   /double(dc->GetDeviceCaps(HORZRES)));
                  y_out_max=double(window_ptr->GetClientRect().Width()-1);
                  z_out_max=double(window_ptr->GetClientRect().Height()-1);
                  if (aspect_ratio*z_out_max*(y_corner_max-y_corner_min)
                   > y_out_max*(z_corner_max-z_corner_min))
                    {
                      pixels_per_unit
                       =y_out_max/(aspect_ratio*(y_corner_max-y_corner_min));
                      y_offset=0.0;
                      z_offset
                       =-(z_out_max-pixels_per_unit*(z_corner_max-z_corner_min))
                       /2.0;
                    }
                  else
                    if (aspect_ratio*z_out_max*(y_corner_max-y_corner_min)
                     < y_out_max*(z_corner_max-z_corner_min))
                      {
                        pixels_per_unit=z_out_max/(z_corner_max-z_corner_min);
                        y_offset=(y_out_max
                         -aspect_ratio*pixels_per_unit
                         *(y_corner_max-y_corner_min))/2.0;
                        z_offset=0.0;
                      }
                    else
                      {
                        pixels_per_unit=1.0;
                        y_offset=y_out_max/2.0;
                        z_offset=-z_out_max/2.0;
                      }
                  x_division_num=y_division_num=0;
                  plot_state='P';
                  break;
                case 'P': // plot
                  finished=FALSE;
                  start_time=GetTickCount();
                  while ((! finished) && (x_division_num < num_x_divisions))
                    {
                      x_division_index
                       =corner[x_division_num][y_division_num].x_division_index;
                      if (x_division_index < (num_x_divisions-1))
                        {
                          y_division_index=corner[x_division_num][
                           y_division_num].y_division_index;
                          if (y_division_index < (num_y_divisions-1))
                            {
                              color_num=int(corner[x_division_index][
                               y_division_index].color);
                              // Adjust contrast.
                              fraction=((double) (color_num-int(color_min)))
                               /((double) (color_max-color_min));
                              if (fraction > 0.0)
                                {
                                  fraction=exp(bias*log(fraction));
                                  color_num=(int)
                                   (((double) (lightest_gray-darkest_gray))
                                   *fraction
                                   +((double) darkest_gray));
                                }
                              else
                                color_num=darkest_gray;
                              vertex[0].y=(double)
                               (corner[x_division_index][y_division_index].y);
                              vertex[0].z=(double) 
                               (corner[x_division_index][y_division_index].z);
                              red_showing=(corner[x_division_index][
                               y_division_index].base_z == (unsigned char) 2);
                              outside_maze=(corner[x_division_index][
                               y_division_index].base_z == (unsigned char) 3);
                              x_division_index++;
                              vertex[1].y=(double)
                               (corner[x_division_index][y_division_index].y);
                              vertex[1].z=(double) 
                               (corner[x_division_index][y_division_index].z);
                              if (red_showing)
                                red_showing=(corner[x_division_index][
                                 y_division_index].base_z == (unsigned char) 2);
                              if (outside_maze)
                                outside_maze=(corner[x_division_index][
                                 y_division_index].base_z == (unsigned char) 3);
                              y_division_index++;
                              vertex[2].y=(double)
                               (corner[x_division_index][y_division_index].y);
                              vertex[2].z=(double)
                               (corner[x_division_index][y_division_index].z);
                              if (red_showing)
                                red_showing=(corner[x_division_index][
                                 y_division_index].base_z == (unsigned char) 2);
                              if (outside_maze)
                                outside_maze=(corner[x_division_index][
                                 y_division_index].base_z == (unsigned char) 3);
                              x_division_index--;
                              vertex[3].y=(double)
                               (corner[x_division_index][y_division_index].y);
                              vertex[3].z=(double)
                               (corner[x_division_index][y_division_index].z);
                              if (red_showing)
                                red_showing=(corner[x_division_index][
                                 y_division_index].base_z == (unsigned char) 2);
                              if (outside_maze)
                                outside_maze=(corner[x_division_index][
                                 y_division_index].base_z == (unsigned char) 3);
                              if (((! only_plot_red) || (red_showing))
                              &&  (! outside_maze))
                               // Plot each point in a quadrilateral.
                                {
                                  for (box_num=0; box_num < 4; box_num++)
                                    {
                                      box[box_num].x=(int) (y_offset
                                       +pixels_per_unit*aspect_ratio
                                       *(vertex[box_num].y-y_corner_min));
                                      box[box_num].y
                                       =(int) (z_offset+z_out_max
                                       -pixels_per_unit
                                       *(vertex[box_num].z-z_corner_min));
                                    }
                                  dc->SelectClipRgn(region_to_paint);
                                  if (use_palette)
                                    {
                                      dc->SelectObject(*palette);
                                      dc->RealizePalette();
                                      if ((show_red) && (red_showing))
                                        {
                                          TColor palColor(num_colors-1);
                                          TBrush brush(palColor);
                                          dc->SelectObject(brush);
                                          TPen pen(palColor,1,PS_NULL);
                                          dc->SelectObject(pen);
                                          dc->Polygon(&box[0],4);
                                          dc->RestorePen();
                                          dc->RestoreBrush();
                                        }
                                      else
                                        {
                                          TColor palColor(color_num);
                                          TBrush brush(palColor);
                                          dc->SelectObject(brush);
                                          TPen pen(palColor,1,PS_NULL);
                                          dc->SelectObject(pen);
                                          dc->Polygon(&box[0],4);
                                          dc->RestorePen();
                                          dc->RestoreBrush();
                                        }
                                      dc->RestorePalette();
                                    }
                                  else
                                    {
                                      if ((show_red) && (red_showing))
                                        {
                                          TBrush brush(RGB(255,0,0));
                                          dc->SelectObject(brush);
                                          TPen pen(RGB(255,0,0),1,PS_NULL);
                                          dc->SelectObject(pen);
                                          dc->Polygon(&box[0],4);
                                          dc->RestorePen();
                                          dc->RestoreBrush();
                                        }
                                      else
                                        {
                                          TBrush brush(RGB(color_num,color_num,
                                           color_num));
                                          dc->SelectObject(brush);
                                          TPen pen(RGB(color_num,color_num,
                                           color_num),1,PS_NULL);
                                          dc->SelectObject(pen);
                                          dc->Polygon(&box[0],4);
                                          dc->RestorePen();
                                          dc->RestoreBrush();
                                        }
                                    }
                                }
                            }
                        }
                      if (++y_division_num >= num_y_divisions)
                        {
                          y_division_num=0;
                          x_division_num++;
                        }
                      current_time=GetTickCount();
                      finished=((current_time < start_time)
                      || ((current_time-start_time) > 100));
                    }
                  if (x_division_num >= num_x_divisions)
                    plot_state='S';
                  break;
                case 'F': // failed
                  result='F';
                  break;
                default:  // done
                  result='S';
                  break;
              }
          delete dc;
        }
      else 
        {
          result='F'; // attempt to plot before prepared
          window_ptr->MessageBox("Attempt to plot before prepared!",
           window_ptr->GetApplication()->GetName(),
           MB_OK | MB_ICONEXCLAMATION);
        }
      return result;
    }


void plot3d::restart_plot()
  {
    if (plot_prepared)
      plot_state='B';
  }
