
#include "xmfract.h"
#include "fractype.h"
#include "macros.h"
#include "helpdefs.h"
#include "mpmath.h"
#include "globals.h"
#include "prototyp.h"

#ifndef NO_IDENT
#ident "$Id: line3d.c,v 1.400 1995/06/20 15:39:23 darryl Exp $"
#endif

/************************************************************************/
/*                                                                      */
/* This file contains a 3D replacement for the out_line function called */
/* by the decoder. The purpose is to apply various 3D transformations   */
/* before displaying points. Called once per line of the input file.    */
/*                                                                      */
/* Original Author Tim Wegner, with extensive help from Marc Reinig.    */
/************************************************************************/

/* static functions */
static void vdraw_line(double *v1, double *v2, int color);
static void draw_light_box(double *origin, double *direct, MATRIX light_m);
static void draw_rect
  (VECTOR V0, VECTOR V1, VECTOR V2, VECTOR V3, int color, int rect);
static void putminmax(int x, int y, int color);
static void putatriangle
  (struct point pt1, struct point pt2, struct point pt3, int color);
static void clipcolor(int x, int y, int color);
static void T_clipcolor(int x, int y, int color);
static void interpcolor(int x, int y, int color);
static int set_pixel_buff
  (unsigned char *pixels, unsigned char *fraction, unsigned linelen);
static void File_Error(char *File_Name1, int ERROR);
static int startdisk1(char *File_Name2, FILE *Source, int overlay);
static int targa_validate(char *File_Name);
static int R_H
  (unsigned char R, unsigned char G, unsigned char B,
   unsigned long *H, unsigned long *S, unsigned long *V);
static int H_R
  (unsigned char *R, unsigned char *G, unsigned char *B,
   unsigned long H, unsigned long S, unsigned long V);
static int RAY_Header(void);
static int out_triangle
  (struct f_point pt1, struct f_point pt2, struct f_point pt3,
   int c1, int c2, int c3);
static void triangle_bounds(float pt_t[3 ][3 ]);
static int start_object(void);
static int end_object(int triout);
static void line3d_cleanup(void);

/* static variables */
void (*mult_vec)(VECTOR) = mult_vec_c;
void (*outln_cleanup)();
void (*normalplot)(int,int,int);
void (*fillplot)(int,int,int);

static char acro_s1[] = {"Set Layer 1\nSet Color 2\nEndpointList X Y Z Name\n"};
static char acro_s2[] = {"LineList From To\n"};
static char s3[] = {"{ Created by xmfract ver. "};
static char s3a[] = {" }\n\n"};
static char banner[] ="%s%#4.2f%s";

static VECTOR light_direction;
static FILE *File_Ptr1;
static VECTOR tmpcross;

static double rXrscale;

static float *costhetaarray;
static float *sinthetaarray;
static float max_xyz[3];
static float min_xyz[3];

static int HAZE_MULT;
static int evenoddrow;
static int line_length1;
static int localpreviewfactor;
static int rand_factor;
static int zcoord = 256;

static long num_tris;

static struct f_point *f_lastrow;
static struct minmax *minmax_x;
static struct point *lastrow;

static unsigned char upr_lwr[4];
static unsigned char Real_Color;
static unsigned char T24;
static unsigned char T32;

static char targa_temp[14];

static unsigned int IAmbient;

int line3d(unsigned char *pixels, unsigned linelen)
{
  /* These variables must preserve their values across calls */
  static float  deltaphi;       /* increment of latitude, longitude */
  static double rscale;         /* surface roughness factor */
  static long xcenter,ycenter;  /* circle center */
  static double sclx,scly,sclz; /* scale factors */
  static double R;              /* radius values */
  static double Rfactor;        /* for intermediate calculation */
  static LMATRIX lm;            /* "" */
  static LVECTOR lview;         /* for perspective views */
  static double zcutoff;        /* perspective backside cutoff value */
  static float twocosdeltaphi;
  static float cosphi,sinphi;   /* precalculated sin/cos of longitude */
  static float oldcosphi1,oldsinphi1;
  static float oldcosphi2,oldsinphi2;
  double r;                     /* sphere radius */
  double xval,yval,zval;        /* rotation values */
  float costheta,sintheta;      /* precalculated sin/cos of latitude */
  float twocosdeltatheta;

  MATRIX lightm;                /* m w/no trans, keeps obj. on screen */
  int next;         /* used by preview and grid */
  int col;          /* current column (original GIF) */
  struct point cur; /* current pixels */
  struct point old; /* old pixels */

  static struct point bad;      /* out of range value */

  struct f_point f_cur;
  struct f_point f_old;
  static struct f_point f_bad;   /* out of range value */

  static unsigned char *fraction;/* float version of pixels array */

  VECTOR v;                      /* double vector */
  VECTOR v1,v2;
  VECTOR crossavg;
  char crossnotinit;             /* flag for crossavg init indication */

  static char msg[128];

  double v_length;
  VECTOR origin, direct, tmp;
  LVECTOR lv;          /* long equivalent of v */
  LVECTOR lv0;         /* long equivalent of v */

  /* corners of transformed xdotx by ydots x colors box */
  double xmin, ymin, zmin, xmax, ymax, zmax;
  int i,j;
  int lastdot;

  long fudge;

  double r0;
  int tout;      /*  triangle has been sent to ray trace file */
  int RND;
  int xcenter0 = 0;
  int ycenter0 = 0;        /* Unfudged versions */
  float f_water = 0.0; /* transformed WATERLINE for ray trace files */

  fudge = 1L<<16;


  if(transparent[0] || transparent[1])
    plot = normalplot = T_clipcolor;  /*  Use transparent plot function */
  else      /* Use the usual xmfract plot function with clipping */
    plot = normalplot = clipcolor;

  currow = rowcount; /* use separate variable to allow for pot16bit files */
  if (pot16bit)
    currow >>= 1;

  /************************************************************************/
  /* This IF clause is executed ONCE per image. All precalculations are   */
  /* done here, with out any special concern about speed. DANGER -        */
  /* communication with the rest of the program is generally via static   */
  /* or global variables.                                                 */
  /************************************************************************/

  if(rowcount++ == 0)
    {
    long check_extra;
    float theta,theta1,theta2;  /* current,start,stop latitude */
    float phi1,phi2;      /* current start,stop longitude */
    float  deltatheta;    /* increment of latitude */
    outln_cleanup = line3d_cleanup;

    calctime = evenoddrow = 0;

    /* mark as in-progress, and enable <tab> timer display */
    calc_status = IMG_ACTV_CALC;
    update_draw_menu(NEW_IMAGE);

    IAmbient = (unsigned int) (255 * (float)(100 - Ambient) / 100.0);

    if(IAmbient < 1)
      IAmbient = 1;

    tout = 0;
    num_tris = 0;

    /* Open file for RAY trace output and write header */
    if (RAY)
      {
      RAY_Header();
      xxadjust = yyadjust = 0; /* Disable shifting in ray tracing */
      xshift = yshift = 0;
      }

    CO_MAX=CO=RO=0;

    upr_lwr[0] = xdots & 0xff;
    upr_lwr[1] = xdots >> 8;
    upr_lwr[2] = ydots &  0xff;
    upr_lwr[3] = ydots >> 8;
    line_length1 = 3 * xdots;    /*  line length @ 3 bytes per pixel  */
    xmerror = 0;

    if (whichimage < 2)
      T_Safe = 0; /* Not safe yet to mess with the source image */

    if (Targa_Out && !((glassestype==1 || glassestype==2) && whichimage==2))
      {
      if (Targa_Overlay)
        {
        /* Make sure target file is a supportable Targa File */
        if(targa_validate (light_name))
          return(-1);
        }
      else
        {
        check_writefile(light_name,".tga");
        if (startdisk1(light_name, NULL, 0))  /* Open new file */
          return(-1);
        }
      }

    rand_factor = 14 - RANDOMIZE;

    zcoord = filecolors;

    crossavg[0] = 0;
    crossavg[1] = 0;
    crossavg[2] = 0;

    /*********************************************************************/
    /*  Memory allocation - assumptions - a 64K segment starting at      */
    /*  extraseg has been grabbed. It may have other purposes elsewhere, */
    /*  but it is assumed that it is totally free for use here. Our      */
    /*  strategy is to assign all the pointers needed here to various    */
    /*  spots in the extra segment, depending on the pixel dimensions of */
    /*  the video mode, and check whether we have run out. There is      */
    /*  basically one case where the extra segment is not big enough     */
    /*  -- SPHERE mode with a fill type that uses putatriangle() (array  */
    /*  minmax_x) at the largest resolution of MAXPIXELSxMAXPIXELS or    */
    /*  thereabouts. In that case we use malloc to grab  memory          */
    /*  for minmax_x. This memory is never freed.                        */
    /*********************************************************************/

    /*
     * lastrow stores the previous row of the original GIF image for
     * the purpose of filling in gaps with triangle procedure
     */

    lastrow = mkptr(extraseg,0);

    check_extra = sizeof(*lastrow)*xdots;
    if(SPHERE)
      {
      sinthetaarray = (float *)(lastrow+xdots);
      check_extra += sizeof(*sinthetaarray)*xdots;

      costhetaarray = (float *)(sinthetaarray+xdots);
      check_extra += sizeof(*costhetaarray)*xdots;

      f_lastrow = (struct f_point *)(costhetaarray+xdots);
      }
    else
      f_lastrow = (struct f_point *)(lastrow+xdots);
    check_extra += sizeof(*f_lastrow)*(xdots);

    if(pot16bit)
      {
      fraction = (unsigned char *)(f_lastrow+xdots);
      check_extra += sizeof(*fraction)*xdots;
      }
    minmax_x = (struct minmax *)NULL;

    /* these fill types call putatriangle which uses minmax_x */
    if( FILLTYPE == 2 || FILLTYPE == 3 || FILLTYPE == 5 || FILLTYPE == 6)
      {
      /* end of arrays if we use extra segement */
      check_extra += sizeof(struct minmax)*ydots;
      if(check_extra > (1L<<16)) /* run out of extra segment? */
        {
        static struct minmax *got_mem = NULL;
        /* not using extra segment so decrement check_extra */
        check_extra -= sizeof(struct minmax)*ydots;
        if(got_mem == NULL)
          got_mem = (struct minmax *)(XtMalloc(MAXPIXELS *
        sizeof(struct minmax)));

        if(got_mem)
          minmax_x = got_mem;
        else
          {
          return(-1);
          }
        }
      else /* ok to use extra segment */
        {
        if(pot16bit)
          minmax_x = (struct minmax *)(fraction+xdots);
        else
          minmax_x = (struct minmax *)(f_lastrow+xdots);
        }
      }

    if(debugflag == 2222 || check_extra > (1L<<16))
      {
      char tmpmsg[70];
      static char extramsg[] = {" of extra segment"};
      sprintf((char *) &tmpmsg,"used %ld%s", check_extra, (char *) &extramsg);
      inform(main_window, (char *) &tmpmsg, NULL);
      }

    /* get scale factors */
    sclx =  XSCALE/100.0;
    scly =  YSCALE/100.0;

    /* if rough=0 make it very flat but plot something */
    if (ROUGH)
      sclz = -ROUGH/100.0;
    else
      rscale = sclz = -0.0001;

    /* aspect ratio calculation - assume screen is 4 x 3 */
    aspect = (double)xdots*.75/(double)ydots;

    if(SPHERE==FALSE)  /* skip this slow stuff in sphere case */
      {
      /*********************************************************************/
      /* What is done here is to create a single matrix, m, which has      */
      /* scale, rotation, and shift all combined. This allows us to use    */
      /* a single matrix to transform any point. Additionally, we create   */
      /* two perspective vectors.                                          */
      /*                                                                   */
      /* Start with a unit matrix. Add scale and rotation. Then calculate  */
      /* the perspective vectors. Finally add enough translation to center */
      /* the final image plus whatever shift the user has set.             */
      /*********************************************************************/

      /* start with identity */
      identity (mtrix);
      identity (lightm);

      /* translate so origin is in center of box, so that when we rotate */
      /* it, we do so through the center */

      trans ( (double)xdots/(-2.0),(double)ydots/(-2.0),
          (double)zcoord/(-2.0),mtrix);
      trans ( (double)xdots/(-2.0),(double)ydots/(-2.0),
          (double)zcoord/(-2.0),lightm);

      /* apply scale factors */
      scale(sclx,scly,sclz,mtrix);
      scale(sclx,scly,sclz,lightm);

      /* rotation values - converting from degrees to radians */
      xval = XROT / 57.29577;
      yval = YROT / 57.29577;
      zval = ZROT / 57.29577;

      if (RAY)  {xval = yval = zval = 0;}

      xrot (xval,mtrix);
      xrot (xval,lightm);
      yrot (yval,mtrix);
      yrot (yval,lightm);
      zrot (zval,mtrix);
      zrot (zval,lightm);

      /* Find values of translation that make all x,y,z negative */
      /* mtrix current matrix */
      /* 0 means don't show box */
      /* returns minimum and maximum values of x,y,z in fractal */
      corners(mtrix,0,&xmin,&ymin,&zmin,&xmax,&ymax,&zmax);
      }

    /* perspective 3D vector - lview[2] == 0 means no perspective */

    /* set perspective flag */
    persp = 0;
    if (ZVIEWER != 0)
      {
      persp = 1;
      if(ZVIEWER < 80) /* force float */
        usr_floatflag |= 2; /* turn on second bit */
      }

    /* set up view vector, and put viewer in center of screen */
    lview[0] = xdots >> 1;
    lview[1] = ydots >> 1;

    /* z value of user's eye - should be more negative than extreme
                negative part of image */
    if(SPHERE) /* sphere case */
      lview[2] = -(long)((double)ydots*(double)ZVIEWER/100.0);
    else  /* non-sphere case */
      lview[2] = (long)((zmin-zmax)*(double)ZVIEWER/100.0);

    view[0] = lview[0];
    view[1] = lview[1];
    view[2] = lview[2];
    lview[0] = lview[0] << 16;
    lview[1] = lview[1] << 16;
    lview[2] = lview[2] << 16;

    if(SPHERE==FALSE) /* sphere skips this */
      {
      /* translate back exactly amount we translated earlier plus enough
        to center image so maximum values are non-positive */
      trans(((double)xdots-xmax-xmin)/2,((double)ydots-ymax-ymin) / 2,
        -zmax,mtrix);

      /* Keep the box centered and on screen regardless of shifts */
      trans(((double)xdots-xmax-xmin)/2,((double)ydots-ymax-ymin)/2,
        -zmax,lightm);

      trans((double)(xshift),(double)(-yshift),0.0,mtrix);

      /* matrix m now contains ALL those transforms composed together !!
        convert m to long integers shifted 16 bits */
      for (i = 0; i < 4; i++)
        for (j = 0; j < 4; j++)
          lm[i][j] = mtrix[i][j] * 65536.0;
      }
    else /* sphere stuff goes here */
      {
      /* Sphere is on side - north pole on right. Top is -90 degrees
        latitude; bottom 90 degrees */

      /* Map X to this LATITUDE range */
      theta1 = THETA1*PI/180.0;
      theta2 = THETA2*PI/180.0;

      /* Map Y to this LONGITUDE range */
      phi1  = PHI1*PI/180.0;
      phi2  = PHI2*PI/180.0;

      theta = theta1;

    /*********************************************************************/
    /* Thanks to Hugh Bray for the following idea: when calculating      */
    /* a table of evenly spaced sines or cosines, only a few initial     */
    /* values need be calculated, and the remaining values can be        */
    /* gotten from a derivative of the sine/cosine angle sum formula     */
    /* at the cost of one multiplication and one addition per value!     */
    /*                                                                   */
    /* This idea is applied once here to get a complete table for        */
    /* latitude, and near the bottom of this routine to incrementally    */
    /* calculate longitude.                                              */
    /*                                                                   */
    /* Precalculate 2*cos(deltaangle), sin(start) and sin(start+delta).  */
    /* Then apply recursively:                                           */
    /*  sin(angle+2*delta) = sin(angle+delta) * 2cosdelta - sin(angle)   */
    /*                                                                   */
    /* Similarly for cosine. Neat!                                       */
    /*********************************************************************/

      deltatheta = (float)(theta2 - theta1)/(float)linelen;

      /* initial sin,cos theta */
      sinthetaarray[0] = sin((double)theta);
      costhetaarray[0] = cos((double)theta);
      sinthetaarray[1] = sin((double)(theta + deltatheta));
      costhetaarray[1] = cos((double)(theta + deltatheta));

      /* sin,cos delta theta */
      twocosdeltatheta = 2.0*cos((double)deltatheta);

      /* build table of other sin,cos with trig identity */
      for(i=2;i<linelen;i++)
        {
        sinthetaarray[i] = sinthetaarray[i-1]*twocosdeltatheta-
          sinthetaarray[i-2];
        costhetaarray[i] = costhetaarray[i-1]*twocosdeltatheta-
          costhetaarray[i-2];
        }

      /* now phi - these calculated as we go - get started here */
      deltaphi  = (float)(phi2  - phi1  )/(float)height;

      /* initial sin,cos phi */

      sinphi = oldsinphi1 = sin((double)phi1);
      cosphi = oldcosphi1 = cos((double)phi1);
      oldsinphi2 = sin((double)(phi1+deltaphi));
      oldcosphi2 = cos((double)(phi1+deltaphi));

      /* sin,cos delta phi */
      twocosdeltaphi = 2*cos((double)deltaphi);

      xcenter0 = xcenter = xdots/2 + xshift;
      ycenter0 = ycenter = ydots/2 - yshift;

      /* affects how rough planet terrain is */
      if (ROUGH)
        rscale = .3*ROUGH/100.0;

      /* radius of planet */
      R = (double)(ydots)/2;

      /* precalculate factor */
      rXrscale = R*rscale;

      sclz = sclx = scly = RADIUS/100.0; /* Need x,y,z for RAY */

      /* adjust x scale factor for aspect */
      sclx *= aspect;

      /* precalculation factor used in sphere calc */
      Rfactor = rscale*R/(double)zcoord;

      if(persp) /* precalculate fudge factor */
        {
        double radius;
        double zview;
        double angle;

        xcenter  = xcenter << 16;
        ycenter  = ycenter << 16;

        Rfactor *= 65536.0;
        R      *= 65536.0;

        /* calculate z cutoff factor
          attempt to prevent out-of-view surfaces from being written */
        zview = -(long)((double)ydots*(double)ZVIEWER/100.0);
        radius = (double)(ydots)/2;
        angle = atan(-radius/(zview+radius));
        zcutoff = -radius - sin(angle)*radius;
        zcutoff *= 1.1; /* for safety */
        zcutoff *= 65536;
        }
      }

    /* set fill plot function */
    if(FILLTYPE != 3)
      fillplot = interpcolor;
    else
      {
      fillplot = clipcolor;

      if(transparent[0] || transparent[1])
        /*  If transparent colors are set */
        fillplot = T_clipcolor;  /*  Use the transparent plot function  */
      }

    /* Both Sphere and Normal 3D */
    direct[0] = light_direction[0] = XLIGHT;
    direct[1] = light_direction[1] = -YLIGHT;
    direct[2] = light_direction[2] = ZLIGHT;

    /* Needed because sclz = -ROUGH/100 and light_direction is transformed
      in FILLTYPE 6 but not in 5. */
    if (FILLTYPE == 5)
      direct[2] = light_direction[2] = -ZLIGHT;

    if(FILLTYPE==6) /* transform light direction */
      {
      /* Think of light direction  as a vector with tail at (0,0,0) and
        head at (light_direction). We apply the transformation to
        BOTH head and tail and take the difference */

      v[0] = 0.0;
      v[1] = 0.0;
      v[2] = 0.0;
      vmult(v,mtrix,v);
      vmult(light_direction,mtrix,light_direction);

      for (i=0;i<3;i++)
        light_direction[i] -= v[i];
      }
    normalize_vector(light_direction);

    if(preview && showbox)
      {
      normalize_vector(direct);

      /* move light vector to be more clear with grey scale maps */
      origin[0] = (3 * xdots) / 16;
      origin[1] = (3 * ydots) / 4;
      if (FILLTYPE == 6)
        origin[1] = (11 * ydots) / 16;

      origin[2] = 0.0;

      v_length = min (xdots, ydots) / 2;
      if (persp && ZVIEWER <= XMP)
        v_length *= (long)(XMP + 600) /((long)(ZVIEWER+600) * 2);

      /* Set direct[] to point from origin[] in direction of
        untransformed light_direction (direct[]). */
      for (i=0;i<3;i++)
        direct[i] = origin[i] + direct[i] * v_length;

      /* center light box */
      for (i=0;i<2;i++)
        {
        tmp[i] = (direct[i] - origin[i]) / 2;
        origin[i] -= tmp[i];
        direct[i] -= tmp[i];
        }

      /*
       * Draw light source vector and box containing it, draw_light_box
       * will transform them if necessary.
       */
      draw_light_box (origin,direct,lightm);

      /*
       * draw box around original field of view to help visualize effect
       * of rotations 1 means show box - xmin etc. do nothing here
       */
      if (!SPHERE)
        corners(mtrix,1,&xmin,&ymin,&zmin,&xmax,&ymax,&zmax);
      }

    /* bad has values caught by clipping */
    f_bad.x      = bad.x      = bad_value;
    f_bad.y      = bad.y      = bad_value;
    f_bad.color = bad.color = bad_value;
    for(i=0;i<linelen;i++)
      {
      lastrow[i]  = bad;
      f_lastrow[i] = f_bad;
      }
    got_status = 3;
    mult_vec = mult_vec_c;
    } /* end of once-per-image intializations */

  crossnotinit = 1;
  col = 0;

  CO = 0;

  /* make sure these pixel coordinates are out of range */
  old    = bad;
  f_old = f_bad;

  /* copies pixels buffer to float type fraction buffer for fill purposes */
  if(pot16bit)
    if (set_pixel_buff(pixels,fraction,linelen))
      {
      return(0);
      }

  /*************************************************************************/
  /* This section of code allows the operation of a preview mode when the  */
  /* preview flag is set. Enabled, it allows the drawing of only the first */
  /* line of the source image, then every 10th line, until and including  */
  /* the last line. For the undrawn lines, only necessary calculations are */
  /* made. As a bonus, in non-sphere mode a box is drawn to help visualize */
  /* the effects of 3D transformations. Thanks to Marc Reinig for this idea*/
  /* and code -- BTW, Marc did NOT put the goto in, but WE did, to avoid  */
  /* copying code here, and to avoid a HUGE "if-then" construct. Besides,  */
  /* we have ALREADY sinned, so why not sin some more?              */
  /*************************************************************************/


  lastdot = min(xdots-1, linelen-1);
  if (FILLTYPE >= 5)
    if (haze && Targa_Out)
      {
      HAZE_MULT = haze * (
        (float)((long)(ydots - 1 - currow) *
        (long)(ydots - 1 - currow)) /
        (float)((long)(ydots - 1) * (long)(ydots - 1)));
      HAZE_MULT = 100 - HAZE_MULT;
      }

  if (previewfactor >= ydots || previewfactor > lastdot)
    previewfactor = min ( ydots - 1, lastdot);

  localpreviewfactor = ydots/previewfactor;

  tout = 0;
  /* Insure last line is drawn in preview and filltypes <0  */
  if ((RAY || preview || FILLTYPE < 0) && (currow != ydots-1) &&
    (currow % localpreviewfactor) && /* Draw mod preview lines */
    !(!RAY && (FILLTYPE > 4) && (currow == 1)))
      /* Get init geometry in lightsource modes */
      goto reallythebottom; /* skip over most of the line3d calcs */

  if(!col && RAY && currow != 0)    start_object();

  if(FILLTYPE==0 && !SPHERE && !pot16bit && !RAY && debugflag != 2224 )
    /* This while loop contains just a limited non-sphere case for speed */
    /* Other while loop still has the old logic. Use debugflag to compare*/
    while(col < linelen)
      {
      Real_Color = cur.color = pixels[col];
      if (cur.color > 0 && cur.color < WATERLINE)
        Real_Color = cur.color = WATERLINE;  /* "lake" */
      if(!usr_floatflag)
        {
        lv0[0] = 0; /* don't save vector before perspective */

        /* use 32-bit multiply math to snap this out */
        lv[0] = col;
        lv[0] = lv[0] << 16;
        lv[1] = currow;
        lv[1] = lv[1] << 16;
        lv[2] = cur.color;
        lv[2] = lv[2] << 16;

        if(longvmultpersp(lv,lm,lv0,lv,lview,16) == -1)
          {
          cur  = bad;
          goto loopbottom;
          }
        cur.x = ((lv[0]/* +32768L */) >> 16) + xxadjust;
        cur.y = ((lv[1]/* +32768L */) >> 16) + yyadjust;
        }
      /* do in float if integer math overflowed */
      if(usr_floatflag || overflow)
        {
        /* slow float version for comparison */
        v[0] = col;
        v[1] = currow;
        v[2] = cur.color;
        mult_vec(v); /* matrix*vector routine */
        if(persp)
          perspective(v);
        cur.x = v[0] + xxadjust /* + .5 */;
        cur.y = v[1] + yyadjust /* + .5 */;
        }
      (*plot)(cur.x,cur.y,cur.color);
      col++;
      }  /*  End of while statement for plotting line  */
  else
    /* PROCESS ROW LOOP BEGINS HERE */
    while(col < linelen)
      {
      if ((RAY || preview || FILLTYPE < 0) &&
        (col != lastdot) &&    /* if this is not the last col */
        /* if not the 1st or mod factor col*/
        (col % (int)(aspect * localpreviewfactor)) &&
        (!(!RAY && FILLTYPE > 4 && col == 1)))
        goto loopbottom;

      f_cur.color = Real_Color = cur.color = pixels[col];

      if (RAY || preview || FILLTYPE < 0)
        {
        next = col + aspect * localpreviewfactor;
        if (next == col)
          next = col + 1;
        }
      else
        next = col + 1;
      if (next >= lastdot)
        next = lastdot;

      if (cur.color > 0 && cur.color < WATERLINE)
        f_cur.color = Real_Color = cur.color = WATERLINE;    /* "lake" */
      else if(pot16bit)
        f_cur.color += ((float)fraction[col])/(float)(1<<8);

      if(SPHERE) /* sphere case */
        {
        sintheta = sinthetaarray[col];
        costheta = costhetaarray[col];

        if(sinphi < 0 && !(RAY || FILLTYPE < 0))
          {
          cur  = bad;
          f_cur = f_bad;
          goto loopbottom;  /* another goto ! */
          }

      /******************************************************************/
      /* KEEP THIS FOR DOCS - original formula --                       */
      /* if(rscale < 0.0)                                               */
      /*      r = 1.0+((double)cur.color/(double)zcoord)*rscale;        */
      /* else                                                           */
      /*      r = 1.0-rscale+((double)cur.color/(double)zcoord)*rscale; */
      /* R = (double)ydots/2;                                           */
      /* r = r*R;                                                       */
      /* cur.x = xdots/2 + sclx*r*sintheta*aspect + xup ;               */
      /* cur.y = ydots/2 + scly*r*costheta*cosphi - yup ;               */
      /******************************************************************/

        if(rscale < 0.0)
          r = R + Rfactor*(double)f_cur.color*costheta;
        else if(rscale > 0.0)
          r = R -rXrscale + Rfactor*(double)f_cur.color*costheta;
        else
          r = R;
        if(persp || RAY) /* Allow Ray trace to go through so display is ok */
          {  /* mrr how do lv[] and cur and f_cur all relate */
          /* NOTE: fudge was pre-calculated above in r and R */
          /* (almost) guarantee negative */
          lv[2] = -R - r*costheta*sinphi;  /* z */
          if((lv[2] > zcutoff) && !FILLTYPE < 0)
            {
            cur = bad;
            f_cur = f_bad;
            goto loopbottom;  /* another goto ! */
            }
          lv[0] = xcenter + sintheta*sclx*r;  /* x */
          lv[1] = ycenter + costheta*cosphi*scly*r;    /* y */

          if((FILLTYPE >= 5) || RAY)
            {  /* calculate illumination normal before persp */

            r0    = r/65536;
            f_cur.x    = xcenter0 + sintheta*sclx*r0;
            f_cur.y    = ycenter0 + costheta*cosphi*scly*r0;
            f_cur.color = -r0*costheta*sinphi;
            }
          if(!(usr_floatflag || RAY))
            {
            if(longpersp(lv,lview,16) == -1)
              {
              cur = bad;
              f_cur = f_bad;
              goto loopbottom;  /* another goto ! */
              }
            cur.x = ((lv[0]+32768L) >> 16) + xxadjust;
            cur.y = ((lv[1]+32768L) >> 16) + yyadjust;
            }
          if(usr_floatflag || overflow || RAY)
            {
            v[0] = lv[0];
            v[1] = lv[1];
            v[2] = lv[2];
            v[0] /= fudge;
            v[1] /= fudge;
            v[2] /= fudge;
            perspective(v);
            cur.x = v[0]+.5 + xxadjust;
            cur.y = v[1]+.5 + yyadjust;
            }
          }
        else if (!(persp && RAY))
          {  /* mrr Not sure how this and 3rd if above relate    */
             /* mrr Why the xx- and yyadjust here and not above? */
          cur.x = f_cur.x = xcenter + sintheta*sclx*r + xxadjust;
          cur.y = f_cur.y = ycenter + costheta*cosphi*scly*r + yyadjust;
          if(FILLTYPE >= 5 || RAY) /* mrr why do we do this for filltype>5? */
            f_cur.color = -r * costheta * sinphi * sclz;
          v[0]=v[1]=v[2]=0;  /* MRR Why do we do this? */
          }
        }
      else /* non-sphere 3D */
        {
        if(!usr_floatflag && !RAY)
          {
          if(FILLTYPE >= 5) /* flag to save vector before perspective */
            lv0[0] = 1;    /* in longvmultpersp calculation */
          else
            lv0[0] = 0;

          /* use 32-bit multiply math to snap this out */
          lv[0] = col;
          lv[0] = lv[0] << 16;
          lv[1] = currow;
          lv[1] = lv[1] << 16;
          if(filetype || pot16bit) /* don't truncate fractional part */
            lv[2] = f_cur.color*65536.0;
          else  /* there IS no fractaional part here! */
            {
            lv[2] = f_cur.color;
            lv[2] = lv[2] << 16;
            }

          if(longvmultpersp(lv,lm,lv0,lv,lview,16) == -1)
            {
            cur  = bad;
            f_cur = f_bad;
            goto loopbottom;
            }

          cur.x = ((lv[0]+32768L) >> 16) + xxadjust;
          cur.y = ((lv[1]+32768L) >> 16) + yyadjust;
          if(FILLTYPE >= 5 && !overflow)
            {
            f_cur.x    = lv0[0];
            f_cur.x      /= 65536.0;
            f_cur.y    = lv0[1];
            f_cur.y      /= 65536.0;
            f_cur.color  = lv0[2];
            f_cur.color /= 65536.0;
            }
          }

        if(usr_floatflag || overflow || RAY)
        /* do in float if integer math overflowed or doing Ray trace */
          {
          /* slow float version for comparison */
          v[0] = col;
          v[1] = currow;
          v[2] = f_cur.color; /* Actually the z value */

          mult_vec(v); /* matrix*vector routine */

          if (FILLTYPE > 4 || RAY)
            {
            f_cur.x  = v[0];
            f_cur.y  = v[1];
            f_cur.color = v[2];

            if(RAY == 6)
              {
              f_cur.x = f_cur.x * (2.0 / xdots) - 1;
              f_cur.y = f_cur.y * (2.0 / ydots) - 1;
              f_cur.color = -f_cur.color * (2.0 / numcolors) - 1;
              }
            }

          if(persp && !RAY)
            perspective(v);
          cur.x = v[0] + xxadjust + .5;
          cur.y = v[1] + yyadjust + .5;

          v[0] = 0;
          v[1] = 0;
          v[2] = WATERLINE;
          mult_vec(v);
          f_water = v[2];
          }
        }

      if (RANDOMIZE)
        if (cur.color > WATERLINE)
          {
          RND = rand15() >> 8;  /* 7-bit number */
          RND = RND * RND >> rand_factor;  /* n-bit number */

          if (rand() & 1)
            RND = -RND; /* Make +/- n-bit number */

          if ((int)(cur.color) + RND >= colors)
            cur.color = colors-2;
          else if ((int)(cur.color) + RND <= WATERLINE)
            cur.color = WATERLINE + 1;
          else
            cur.color = cur.color + RND;
          Real_Color = cur.color;
          }

      if (RAY)
        {
        if (col && currow &&
          old.x > bad_check &&
          old.x < (xdots - bad_check) &&
          lastrow[col].x > bad_check &&
          lastrow[col].y > bad_check &&
          lastrow[col].x < (xdots - bad_check) &&
          lastrow[col].y < (ydots - bad_check))
          {
          /* Get rid of all the triangles in the plane
            at the base of the object */

          if (f_cur.color == f_water &&
            f_lastrow[col].color == f_water &&
            f_lastrow[next].color == f_water)
              goto loopbottom;

          if (RAY != 6) /* Output the vertex info */
            out_triangle(f_cur, f_old, f_lastrow[col],
              cur.color, old.color, lastrow[col].color);

          tout = 1;

          draw_line (old.x, old.y, cur.x, cur.y, old.color);
          draw_line (old.x, old.y, lastrow[col].x,
            lastrow[col].y, old.color);
          draw_line (lastrow[col].x, lastrow[col].y,
            cur.x, cur.y, cur.color);
          num_tris++;
          }

        if (col < lastdot && currow &&
          lastrow[col].x > bad_check &&
          lastrow[col].y > bad_check &&
          lastrow[col].x < (xdots - bad_check) &&
          lastrow[col].y < (ydots - bad_check) &&
          lastrow[next].x > bad_check &&
          lastrow[next].y > bad_check &&
          lastrow[next].x < (xdots - bad_check) &&
          lastrow[next].y < (ydots - bad_check))
          {
          /*
           * Get rid of all the triangles in the plane at the
           * base of the object
           */

          if (f_cur.color == f_water &&
            f_lastrow[col].color == f_water &&
            f_lastrow[next].color == f_water)
              goto loopbottom;

          if (RAY != 6) /* Output the vertex info */
            out_triangle(f_cur, f_lastrow[col], f_lastrow[next],
              cur.color, lastrow[col].color, lastrow[next].color);

          tout = 1;

          draw_line (lastrow[col].x, lastrow[col].y, cur.x, cur.y,
            cur.color);
          draw_line (lastrow[next].x, lastrow[next].y, cur.x, cur.y,
            cur.color);
          draw_line (lastrow[next].x, lastrow[next].y, lastrow[col].x,
            lastrow[col].y, lastrow[col].color);
          num_tris++;
          }

        if (RAY == 6)  /* Output vertex info for Acrospin */
          {
          fprintf (File_Ptr1, "% #4.4f % #4.4f % #4.4f R%dC%d\n",
            f_cur.x, f_cur.y, f_cur.color, RO, CO);
          if (CO > CO_MAX)
            CO_MAX = CO;
          CO++;
          }
        goto loopbottom;
        }

      switch(FILLTYPE)
        {
        case -1:
          if (col &&
          old.x > bad_check &&
          old.x < (xdots - bad_check))
            draw_line (old.x, old.y, cur.x, cur.y, cur.color);
          if (currow &&
          lastrow[col].x > bad_check &&
          lastrow[col].y > bad_check &&
          lastrow[col].x < (xdots - bad_check) &&
          lastrow[col].y < (ydots - bad_check))
            draw_line (lastrow[col].x,lastrow[col].y,cur.x,
              cur.y,cur.color);
        break;

        case 0:
          (*plot)(cur.x,cur.y,cur.color);
        break;

        case 1:      /* connect-a-dot */
          if ((old.x < xdots) && (col) &&
            old.x > bad_check &&
            old.y > bad_check) /* Don't draw from old to cur on col 0 */
              draw_line(old.x,old.y,cur.x,cur.y,cur.color);
        break;

        case 2: /* with interpolation */
        case 3: /* no interpolation */
          /***************************************************************/
          /*  "triangle fill" - consider four points: current point,     */
          /*  previous point same row, point opposite current point in   */
          /*  previous row, point after current point in previous row.   */
          /*  The object is to fill all points inside the two triangles. */
          /*                                                             */
          /*  lastrow[col].x/y___ lastrow[next]                          */
          /*        /  1   /                                             */
          /*        /  1   /                                             */
          /*        /  1   /                                             */
          /*  oldrow/col _____ trow/col                                  */
          /***************************************************************/

          if(currow && !col)
            putatriangle(lastrow[next],lastrow[col],cur,cur.color);
          if(currow && col)    /* skip first row and first column */
            {
            if(col == 1)
              putatriangle(lastrow[col],oldlast,old,old.color);

            if(col < lastdot)
              putatriangle(lastrow[next],lastrow[col], cur, cur.color);
            putatriangle(old, lastrow[col], cur, cur.color);
            }
        break;

        case 4: /* "solid fill" */
          if (SPHERE)
            {
            if (persp)
              {
              old.x = xcenter>>16;
              old.y = ycenter>>16;
              }
            else
              {
              old.x = xcenter;
              old.y = ycenter;
              }
            }
          else
            {
            lv[0] = col;
            lv[1] = currow;
            lv[2] = 0;

            /* apply fudge bit shift for integer math */
            lv[0] = lv[0] << 16;
            lv[1] = lv[1] << 16;
            /* Since 0, unnecessary lv[2] = lv[2] << 16;*/

            if(longvmultpersp(lv,lm,lv0,lv,lview,16))
              {
              cur  = bad;
              f_cur = f_bad;
              goto loopbottom;  /* another goto ! */
              }

            /*  Round and fudge back to original  */
            old.x = (lv[0]+32768L) >> 16;
            old.y = (lv[1]+32768L) >> 16;
            }
          if (old.x < 0)
            old.x = 0;
          if (old.x >= xdots)
            old.x = xdots-1;
          if (old.y < 0)
            old.y = 0;
          if (old.y >= ydots)
            old.y = ydots-1;
          draw_line(old.x,old.y,cur.x,cur.y,cur.color);
        break;

        case 5:
        case 6:
          /* light-source modulated fill */
          if(currow && col)    /* skip first row and first column */
            {
            if(f_cur.color < bad_check || f_old.color < bad_check ||
              f_lastrow[col].color < bad_check)
                break;

            v1[0] = f_cur.x    - f_old.x;
            v1[1] = f_cur.y    - f_old.y;
            v1[2] = f_cur.color - f_old.color;

            v2[0] = f_lastrow[col].x      - f_cur.x;
            v2[1] = f_lastrow[col].y      - f_cur.y;
            v2[2] = f_lastrow[col].color - f_cur.color;

            cross_product (v1, v2, cross);

            /* normalize cross - and check if non-zero */
            if(normalize_vector(cross))
              {
              if(debugflag)
                {
                sprintf((char *) &msg, "debug, cur.color=bad");
                inform(main_window, (char *) &msg, NULL);
                }
              cur.color = f_cur.color = bad.color;
              }
            else
              {
              /* line-wise averaging scheme */
              if(LIGHTAVG>0)
                {
                if(crossnotinit)
                  {
                  /* initialize array of old normal vectors */
                  crossavg[0] = cross[0];
                  crossavg[1] = cross[1];
                  crossavg[2] = cross[2];
                  crossnotinit = 0;
                  }
                tmpcross[0] = (crossavg[0]*LIGHTAVG+cross[0]) /
                   (LIGHTAVG+1);
                tmpcross[1] = (crossavg[1]*LIGHTAVG+cross[1]) /
                   (LIGHTAVG+1);
                tmpcross[2] = (crossavg[2]*LIGHTAVG+cross[2]) /
                   (LIGHTAVG+1);

                cross[0] = tmpcross[0];
                cross[1] = tmpcross[1];
                cross[2] = tmpcross[2];

                if(normalize_vector(cross))
                  {
                  /* this shouldn't happen */
                  if(debugflag)
                    {
                    sprintf((char *) &msg, "debug, normal vector err2");
                    inform(main_window, (char *) &msg, NULL);
                    /* use next instead if you ever need details:
                    static char tmp[] = {"debug, vector err"};
                    sprintf((char *) &msg,"%s\n%f %f %f\n%f %f %f\n%f %f %f",
                    tmp, f_cur.x, f_cur.y, f_cur.color,
                    f_lastrow[col].x, f_lastrow[col].y,
                    f_lastrow[col].color, f_lastrow[col-1].x,
                    f_lastrow[col-1].y,f_lastrow[col-1].color);
                    inform(main_window, (char *) &msg, NULL);
                    */
                    }
                  cur.color = f_cur.color = colors;
                  }
                }
              crossavg[0] = tmpcross[0];
              crossavg[1] = tmpcross[1];
              crossavg[2] = tmpcross[2];

              /* dot product of unit vectors is cos of angle between */
              /* we will use this value to shade surface */

              cur.color = 1 + (colors-2) *
                (1.0-dot_product(cross,light_direction));
              }
            /* if colors out of range, set them to min or max color
              index but avoid background index. This makes colors
              "opaque" so SOMETHING plots. These conditions shouldn't
              happen but just in case          */
            if(cur.color < 1)      /* prevent transparent colors */
              cur.color = 1;    /* avoid background */
            if(cur.color > colors-1)
              cur.color = colors-1;

          /* why "col < 2"? So we have sufficient geometry for the fill */
          /* algorithm, which needs previous point in same row to have  */
          /* already been calculated (variable old)    */
          /* fix ragged left margin in preview */
            if (col == 1 && currow > 1)
              putatriangle(lastrow[next],lastrow[col],cur,cur.color);

            if(col < 2 || currow < 2) /* don't have valid colors yet */
              break;

            if(col < lastdot)
              putatriangle(lastrow[next],lastrow[col],cur,cur.color);
            putatriangle(old,lastrow[col],cur,cur.color);

            plot=standardplot;
            }
        break;
        }  /*  End of CASE statement for fill type  */

loopbottom:

      if (RAY || (FILLTYPE != 0 && FILLTYPE != 4))
        {
        /* for triangle and grid fill purposes */
        oldlast = lastrow[col];
        old = lastrow[col] = cur;

        /* for illumination model purposes */
        f_old  = f_lastrow[col] = f_cur;
        if (currow && RAY && col >= lastdot)
        /* if we're at the end of a row, close the object */
          {
          end_object(tout);
          tout=0;
          if (ferror (File_Ptr1))
            {
            fclose (File_Ptr1);
            remove (light_name);
            File_Error(ray_name, 2);
            return(-1);
            }
          }
        }

      col++;

      }  /*  End of while statement for plotting line  */

  RO++;

reallythebottom:

  /* stuff that HAS to be done, even in preview mode, goes here */
  if(SPHERE)
    {
    /* incremental sin/cos phi calc */
    if(currow == 0)
      {
      sinphi = oldsinphi2;
      cosphi = oldcosphi2;
      }
    else
      {
      sinphi = twocosdeltaphi*oldsinphi2 - oldsinphi1;
      cosphi = twocosdeltaphi*oldcosphi2 - oldcosphi1;
      oldsinphi1 = oldsinphi2;
      oldsinphi2 = sinphi;
      oldcosphi1 = oldcosphi2;
      oldcosphi2 = cosphi;
      }
    }
  return(0); /* decoder needs to know all is well !!! */
}

/* vector version of line draw */
static void vdraw_line(v1,v2,color)
double *v1,*v2;
int color;
{
  int x1,y1,x2,y2;
  x1 = (int)v1[0];
  y1 = (int)v1[1];
  x2 = (int)v2[0];
  y2 = (int)v2[1];
  draw_line(x1,y1,x2,y2,color);
}



void corners(m,show,pxmin,pymin,pzmin,pxmax,pymax,pzmax)

MATRIX m;
int show; /* turns on box-showing feature */
double *pxmin,*pymin,*pzmin,*pxmax,*pymax,*pzmax;

{
  int i,j;
  VECTOR S[2][4]; /* Holds the top an bottom points, S[0][]=bottom */

  /*
  define corners of box fractal is in in x,y,z plane
  "b" stands for "bottom" - these points are the corners of the screen
  in the x-y plane. The "t"'s stand for Top - they are the top of
  the cube where 255 color points hit.
  */

  *pxmin = *pymin = *pzmin = (int)INT_MAX;
  *pxmax = *pymax = *pzmax = (int)INT_MIN;

  for (j = 0; j < 4; ++j)
    for (i=0;i<3;i++)
      S[0][j][i]=S[1][j][i]=0;

  S[0][1][0] = S[0][2][0] = S[1][1][0] = S[1][2][0] = xdots-1;
  S[0][2][1] = S[0][3][1] = S[1][2][1] = S[1][3][1] = ydots-1;
  S[1][0][2] = S[1][1][2] = S[1][2][2] = S[1][3][2] = zcoord-1;

  for (i = 0; i < 4; ++i)
    {
    /* transform points */
    vmult(S[0][i],m,S[0][i]);
    vmult(S[1][i],m,S[1][i]);

    /* update minimums and maximums */
    if (S[0][i][0] <= *pxmin) *pxmin = S[0][i][0];
    if (S[0][i][0] >= *pxmax) *pxmax = S[0][i][0];
    if (S[1][i][0] <= *pxmin) *pxmin = S[1][i][0];
    if (S[1][i][0] >= *pxmax) *pxmax = S[1][i][0];
    if (S[0][i][1] <= *pymin) *pymin = S[0][i][1];
    if (S[0][i][1] >= *pymax) *pymax = S[0][i][1];
    if (S[1][i][1] <= *pymin) *pymin = S[1][i][1];
    if (S[1][i][1] >= *pymax) *pymax = S[1][i][1];
    if (S[0][i][2] <= *pzmin) *pzmin = S[0][i][2];
    if (S[0][i][2] >= *pzmax) *pzmax = S[0][i][2];
    if (S[1][i][2] <= *pzmin) *pzmin = S[1][i][2];
    if (S[1][i][2] >= *pzmax) *pzmax = S[1][i][2];
    }

  if(show)
    {
    if(persp)
      {
      for (i=0;i<4;i++)
        {
        perspective(S[0][i]);
        perspective(S[1][i]);
        }
      }

    /* Keep the box surrounding the fractal */
    for (j=0;j<2;j++)
      for (i = 0; i < 4; ++i)
        {
        S[j][i][0] += xxadjust;
        S[j][i][1] += yyadjust;
        }

    draw_rect(S[0][0],S[0][1],S[0][2],S[0][3],2,1);/* Bottom */

    draw_rect(S[0][0],S[1][0],S[0][1],S[1][1], 5, 0); /* Sides */
    draw_rect(S[0][2],S[1][2],S[0][3],S[1][3], 6, 0);

    draw_rect(S[1][0],S[1][1],S[1][2],S[1][3],8,1); /* Top */
    }
}

/* This function draws a vector from origin[] to direct[] and a box
  around it. The vector and box are transformed or not depending on
  FILLTYPE.
*/

static void draw_light_box (origin, direct, light_m)
double *origin, *direct;
MATRIX light_m;
{
  VECTOR S[2][4];
  int i,j;
  double temp;

  S[1][0][0] = S[0][0][0] = origin[0];
  S[1][0][1] = S[0][0][1] = origin[1];

  S[1][0][2] = direct[2];

  for (i=0;i<2;i++)
    {
    S[i][1][0] = S[i][0][0];
    S[i][1][1] = direct[1];
    S[i][1][2] = S[i][0][2];
    S[i][2][0] = direct[0];
    S[i][2][1] = S[i][1][1];
    S[i][2][2] = S[i][0][2];
    S[i][3][0] = S[i][2][0];
    S[i][3][1] = S[i][0][1];
    S[i][3][2] = S[i][0][2];
    }

  /* transform the corners if necessary */
  if (FILLTYPE == 6)
    for (i=0;i<4;i++)
      {
      vmult (S[0][i],light_m,S[0][i]);
      vmult (S[1][i],light_m,S[1][i]);
      }

  /* always use perspective to aid viewing */
  temp = view[2]; /* save perspective distance for a later restore */
  view[2] = - XMP * 300.0 / 100.0;

  for (i=0;i<4;i++)
    {
    perspective(S[0][i]);
    perspective(S[1][i]);
    }
  view[2] = temp; /* Restore perspective distance*/

  /* Adjust for aspect */
  for (i=0;i<4;i++)
    {
    S[0][i][0] = S[0][i][0] * aspect;
    S[1][i][0] = S[1][i][0] * aspect;
    }

  /* draw box connecting transformed points. NOTE order and COLORS */
  draw_rect(S[0][0],S[0][1],S[0][2],S[0][3],2,1);

  vdraw_line (S[0][0],S[1][2],8);

  /* sides */
  draw_rect(S[0][0],S[1][0],S[0][1],S[1][1], 4, 0);
  draw_rect(S[0][2],S[1][2],S[0][3],S[1][3], 5, 0);

  draw_rect(S[1][0],S[1][1],S[1][2],S[1][3],3,1);

  /* Draw the "arrow head" */
  for (i= -3;i<4;i++)
    for (j= -3;j<4;j++)
      if (abs(i) + abs(j) < 6)
        plot((int)(S[1][2][0]+i),(int)(S[1][2][1]+j),10);
}

static void draw_rect (V0, V1, V2, V3, color, rect)

VECTOR V0, V1, V2, V3;
int color, rect;
{
VECTOR V[4];
int i;

  for (i=0;i<2;i++)
    { /* Since V[2] is not used by vdraw_line don't bother setting it */
    V[0][i] = V0[i];
    V[1][i] = V1[i];
    V[2][i] = V2[i];
    V[3][i] = V3[i];
    }
  if (rect) /* Draw a rectangle */
    {
    for (i=0;i<4;i++)
      if (fabs(V[i][0]-V[(i+1)%4][0]) < -2*bad_check &&
        fabs(V[i][1]-V[(i+1)%4][1]) < -2*bad_check)
          vdraw_line (V[i],V[(i+1)%4],color);
    }
  else /* Draw 2 lines instead */
    {
    for(i=0;i<3;i+=2)
      if (fabs(V[i][0]-V[i+1][0]) < -2*bad_check &&
        fabs(V[i][1]-V[i+1][1]) < -2*bad_check)
          vdraw_line (V[i],V[i+1],color);
    }
  return;
}

/* replacement for plot - builds a table of min and max x's instead of plot */
/* called by draw_line as part of triangle fill routine */

/*ARGSUSED*/
static void putminmax(int x,int y,int color)
{
  if(y >= 0 && y < ydots)
    {
    if(x < minmax_x[y].minx) minmax_x[y].minx = x;
    if(x > minmax_x[y].maxx) minmax_x[y].maxx = x;
    }
}

/*
  This routine fills in a triangle. Extreme left and right values for
  each row are calculated by calling the line function for the sides.
  Then rows are filled in with horizontal lines
*/

static void putatriangle(struct point pt1,struct point pt2,
          struct point pt3,int color)
{
  int miny,maxy;
  int x,y,xlim;

  /* Too many points off the screen? */
  if(offscreen(pt1) + offscreen(pt2) + offscreen(pt3) > MAXOFFSCREEN)
    return;

  p1 = pt1; /* needed by interpcolor */
  p2 = pt2;
  p3 = pt3;

  /* fast way if single point or single line */
  if (p1.y == p2.y && p1.x == p2.x)
    {
    plot = fillplot;
    if (p1.y == p3.y && p1.x == p3.x)
      (*plot)(p1.x, p1.y, color);
    else
      draw_line(p1.x,p1.y,p3.x,p3.y,color);
    plot = normalplot;
    return;
    }
  else if ( (p3.y == p1.y && p3.x == p1.x) || (p3.y == p2.y && p3.x == p2.x) )
    {
    plot = fillplot;
    draw_line(p1.x,p1.y,p2.x,p2.y,color);
    plot = normalplot;
    return;
    }

  /* find min max y */
  miny = maxy = p1.y;
  if(p2.y < miny)
    miny = p2.y;
  else
    maxy = p2.y;
  if(p3.y < miny)
    miny = p3.y;
  else if(p3.y > maxy)
    maxy = p3.y;

  /* only worried about values on screen */
  if(miny < 0)
    miny = 0;
  if(maxy >= ydots)
    maxy = ydots-1;

  for(y=miny;y<=maxy;y++)
    {
    minmax_x[y].minx = (int)INT_MAX;
    minmax_x[y].maxx = (int)INT_MIN;
    }

  /* set plot to "fake" plot function */
  plot = putminmax;

  /* build table of extreme x's of triangle */
  draw_line(p1.x,p1.y,p2.x,p2.y,0);
  draw_line(p2.x,p2.y,p3.x,p3.y,0);
  draw_line(p3.x,p3.y,p1.x,p1.y,0);

  for(y=miny;y<=maxy;y++)
    {
    xlim = minmax_x[y].maxx;
    for(x=minmax_x[y].minx;x<=xlim;x++)
    (*fillplot)(x,y,color);
    }
  plot = normalplot;
}

int offscreen(struct point pt)
{
  if(pt.x >= 0)
    if(pt.x < xdots)
      if(pt.y >= 0)
        if(pt.y < ydots)
          return(0); /* point is ok */
  if (abs(pt.x) > 0-bad_check || abs(pt.y) > 0-bad_check)
    return(99); /* point is bad */
  return(1); /* point is off the screen */
}

static void clipcolor(int x,int y,int color)
{
  if(0 <= x  && x < xdots  &&
    0 <= y  && y < ydots  &&
    0 <= color && color < filecolors)
    {
    standardplot(x,y,color);

    if (Targa_Out)
      if (!(glassestype==1 || glassestype==2))
        /* standardplot modifies color in these types */
        targa_color(x, y, color);
    }
}

/*********************************************************************/
/* This function is the same as clipcolor but checks for color being */
/* in transparent range. Intended to be called only if transparency  */
/* has been enabled.                         */
/*********************************************************************/

static void T_clipcolor(int x,int y,int color)
{
  if (0 <= x    && x < xdots      && /*  is the point on screen?  */
    0 <= y  && y < ydots      && /*  Yes?  */
    0 <= color && color < colors  && /*  Colors in valid range?  */
    /*  Lets make sure its not a transparent color  */
    (transparent[0] > color || color > transparent[1]))
    {
    standardplot(x,y,color); /* I guess we can plot then  */

    if (Targa_Out)
      if (!(glassestype==1 || glassestype==2))
        /* standardplot modifies color in these types */
        targa_color(x, y, color);
    }
}

/************************************************************************/
/* A substitute for plotcolor that interpolates the colors according  */
/* to the x and y values of three points (p1,p2,p3) which are static in  */
/* this routine                              */
/*                                    */
/*  In Light source modes, color is light value, not actual color    */
/*  Real_Color always contains the actual color              */
/************************************************************************/

static void interpcolor(int x,int y,int color)
{
  int D,d1,d2,d3;

  /* this distance formula is not the usual one - but it has the virtue
    that it uses ONLY additions (almost) and it DOES go to zero as the
    points get close.
    */

  d1 = abs(p1.x-x)+abs(p1.y-y);
  d2 = abs(p2.x-x)+abs(p2.y-y);
  d3 = abs(p3.x-x)+abs(p3.y-y);

  D = (d1 + d2 + d3) << 1;
  if(D)
    {  /* calculate a weighted average of colors -
    long casts prevent integer overflow. This can evaluate to zero */
    color = ((long)(d2+d3)*(long)p1.color +
      (long)(d1+d3)*(long)p2.color +
      (long)(d1+d2)*(long)p3.color) / D;
    }

  if(0 <= x    && x < xdots  &&
    0 <= y    && y < ydots  &&
    0 <= color && color < colors &&
    (transparent[1] == 0 || (int) Real_Color > transparent[1] ||
    transparent[0] > (int) Real_Color))
    {
    if (Targa_Out)
      if (!(glassestype==1 || glassestype==2))
        /* standardplot modifies color in these types */
        D = targa_color(x, y, color);

    if (FILLTYPE >= 5)
      if (Real_V && Targa_Out)
        color = D;
      else
        {
        color =  (1 + (unsigned)color * IAmbient)/256;
        if (color == 0)
          color = 1;
        }
    standardplot(x,y,color);
    }
}

/*
  In non light source modes, both color and Real_Color contain the
  actual pixel color. In light source modes, color contains the
  light value, and Real_Color contains the origninal color

  This routine takes a pixel modifies it for lightshading if appropriate
  and plots it in a Targa file. Used in plot3d.c
*/

int targa_color(int x,int y,int color)
{
  unsigned long H, S, V;
  unsigned char RGB[3];

  if (FILLTYPE == 2 || glassestype==1 || glassestype==2)
    Real_Color = color; /* So Targa gets interpolated color */

  RGB[0]  =  dacbox [Real_Color] [0];
  RGB[1]  =  dacbox [Real_Color] [1];
  RGB[2]  =  dacbox [Real_Color] [2];

  /* Now lets convert it to HSV */
  R_H(RGB[0], RGB[1], RGB[2], &H, &S, &V);

  /* Modify original S and V components */
  if (FILLTYPE > 4 && !(glassestype==1 || glassestype==2))
    /* Adjust for Ambient */
    V = (V * (65535 - (unsigned)(color * IAmbient))) / 65535;

  if (haze)
    {
    S = (unsigned long)(S * HAZE_MULT) / 100; /* Haze lowers sat of colors */
    if (V >= 32640) /* Haze reduces contrast */
      {
      V = V - 32640;
      V = (unsigned long)((V * HAZE_MULT) / 100);
      V = V + 32640;
      }
    else
      {
      V = 32640 - V;
      V = (unsigned long)((V * HAZE_MULT) / 100);
      V = 32640 - V;
      }
    }
  /* Now lets convert it back to RGB. Original Hue, modified Sat and Val */
  H_R(&RGB[0], &RGB[1], &RGB[2], H, S, V);

  if (Real_V)
    V = (35 * (int)RGB[0] + 45 * (int)RGB[1] + 20 * (int)RGB[2]) / 100;

  /* Now write the color triple to its transformed location */
  /* on the disk. */
  targa_writedisk((unsigned int)(x+sxoffs), (unsigned int)(y+syoffs),
    RGB[0], RGB[1], RGB[2]);

  return(255-V);
}

static int set_pixel_buff
  (unsigned char *pixels,unsigned char *fraction,unsigned linelen)
{
  int i;
  if ((evenoddrow++ & 1) == 0) /* even rows are color value */
    {
    for(i=0;i<linelen;i++) /* add the fractional part in odd row */
      fraction[i] = pixels[i];
    return(1);
    }
  else /* swap */
    {
    unsigned char tmp;
    for(i=0;i<linelen;i++) /* swap so pixel has color */
      {
      tmp = pixels[i];
      pixels[i]=fraction[i];
      fraction[i]=tmp;
      }
    }
  return(0);
}

/* cross product  - useful because cross is perpendicular to v and w */
/**** PB commented this out - it is unused
int chk_cross_product (int col, int row,VECTOR v, VECTOR w, VECTOR cross, VECTOR crossavg)
{
  static start = 1;
  static FILE *fp;

  if(start)
    {
    fp = fopen("blob","w");
    start = 0;
    }

  fprintf(fp,"row %+4d col %+4d v1 %+8.3e %+8.3e %+8.3e v2 %+8.3e %+8.3e %+8.3e cross %+8.3e %+8.3e %+8.3e crossavg %+8.3e %+8.3e %+8.3e \n",
  row,col,v[0],v[1],v[2],w[0],w[1],w[2],cross[0],cross[1],cross[2],crossavg[0],crossavg[1],crossavg[2]);
  return(0);
}
***/

/**************************************************************************
    Common routine for printing error messages to the screen for Targa
    and other files
**************************************************************************/

static char fs[]        = "%s%s";
static char fff[]         = "%s%s%s";
static char OOPS[]  = {"OOPS, "};
static char E1[]    = {"can't handle this type of file.\n"};
static char str1[]  = {"couldn't open  < "};
static char str3[]  = {"image wrong size\n"};
static char tempx[] = {"ran out of disk space. < "};

static void File_Error (char *File_Name1, int ERROR)
{
   char msgbuf[200];

  xmerror = ERROR;
  switch(ERROR)
    {
    case 1: /* Can't Open */
      sprintf(msgbuf,"%s%s%s >", OOPS, str1, File_Name1);
    break;
    case 2: /* Not enough room */
      sprintf(msgbuf,"%s%s%s >", OOPS, tempx, File_Name1);
    break;
    case 3: /* Image wrong size */
      sprintf(msgbuf, fs, OOPS, str3);
    break;
    case 4: /* Wrong file type */
      sprintf(msgbuf, fs, OOPS, E1);
    break;
    }
  warning(main_window, msgbuf, NULL);
  return;
}

/************************************************************************/
/*                                                                      */
/*    This function opens a TARGA_24 file for reading and writing. If   */
/*    its a new file, (overlay == 0) it writes a header. If it is to    */
/*    overlay an existing file (overlay == 1) it copies the original    */
/*    header whose lenght and validity was determined in                */
/*    Targa_validate.                                                   */
/*                                                                      */
/*    It Verifies there is enough disk space, and leaves the file       */
/*    at the start of the display data area.                            */
/*                                                                      */
/*    If this is an overlay, closes source and copies to "targa_temp"   */
/*    If there is an error close the file.                              */
/*                                                                      */
/* **********************************************************************/

static int startdisk1 (char *File_Name2, FILE *Source, int overlay)
{
  int i,j,k, inc;
  FILE *fps;

  /* Open File for both reading and writing */
  if ((fps=fopen(File_Name2,"w+b"))==NULL)
    {
    File_Error(File_Name2, 1);
    return(-1); /* Oops, somethings wrong! */
    }

  inc = 1; /* Assume we are overlaying a file */

  /* Write the header */
  if (overlay) /* We are overlaying a file */
    for (i=0;i<T_header_24;i++) /* Copy the header from the Source */
      fputc(fgetc(Source),fps);
  else
    {  /* Write header for a new file */
    /* ID field size = 0, No color map, Targa type 2 file */
    for (i=0;i<12;i++)
      {
      if (i == 2)
        fputc(i,fps);
      else
        fputc(0,fps);
      }
    /*  Write image size  */
    for (i=0;i<4;i++)
      fputc(upr_lwr[i],fps);
    fputc(T24,fps); /* Targa 24 file */
    fputc(T32,fps); /* Image at upper left */
    inc = 3;
    }

  /*  Finished with the header, now lets work on the display area  */
  for (i=0;i<ydots;i++)  /* "clear the screen" (write to the disk) */
    {
    for (j=0;j<line_length1;j=j+inc)
      {
      if (overlay)
        fputc(fgetc(Source), fps);
      else
        for (k=2;k>-1;k--)
          fputc(back_color[k], fps);      /* Targa order (B, G, R) */
      }
    if (ferror (fps))
      {
      /*  Almost certainly not enough disk space  */
      fclose (fps);
      fclose (Source);
      remove (File_Name2);
      File_Error(File_Name2, 2);
      return(-2);
      }
    if (check_key() != 0)
      return(-3);
    }

  if (targa_startdisk(fps, T_header_24) != 0)
    {
    enddisk();
    remove (File_Name2);
    return(-4);
    }
  return(0);
}

static int targa_validate(char *File_Name)
{
  FILE *fp;
  int i, j = 0;

  /* Attempt to open source file for reading */
  if ((fp=fopen(File_Name,"rb"))==NULL)
    {
    File_Error(File_Name, 1);
    return(-1); /* Oops, file does not exist */
    }

  T_header_24 += fgetc(fp); /* Check ID field and adjust header size */

  if (fgetc(fp)) /* Make sure this is an unmapped file */
    {
    File_Error(File_Name, 4);
    return(-1);
    }

  if (fgetc(fp)!=2) /* Make sure it is a type 2 file */
    {
    File_Error(File_Name, 4);
    return(-1);
    }

  /* Skip color map specification */
  for (i=0;i<5;i++)
      fgetc(fp);

  for (i=0;i<4;i++)
    {
    /* Check image origin */
    fgetc(fp);
    if(j != 0)
      {
      File_Error(File_Name, 4);
      return(-1);
      }
    }
  /* Check Image specs */
  for (i=0;i<4;i++)
    if(fgetc(fp) != upr_lwr[i])
      {
      File_Error(File_Name,3);
      return(-1);
      }

  if(fgetc(fp) != T24) xmerror=4; /* Is it a targa 24 file? */
  if(fgetc(fp) != T32) xmerror=4; /* Is the origin at the upper left? */
  if (xmerror == 4)
    {
    File_Error(File_Name,4);
    return(-1);
    }
  rewind(fp);

  /* Now that we know its a good file, create a working copy */
  if (startdisk1(targa_temp, fp, 1))
    return(-1);

  fclose(fp);  /* Close the source */

  T_Safe=1; /* Original file successfully copied to targa_temp */
  return(0);
}

static int R_H (unsigned char R, unsigned char G, unsigned char B,
         unsigned long *H, unsigned long *S, unsigned long *V)
{
  unsigned long  R1, G1, B1, DENOM;
  unsigned char MINIM, TMPVAL;

  TMPVAL = R;
  *V = TMPVAL;
  MINIM = G;
  if (R < G)
    {
    TMPVAL = G;
    *V = TMPVAL;
    MINIM = R;
    if (G < B)
      {
      TMPVAL = B;
      *V = TMPVAL;
      }
    if (B < R)
      MINIM = B;
    }
  else
    {
    if (B < G)
      MINIM = B;
    if (R < B)
      {
      TMPVAL = B;
      *V = TMPVAL;
      }
    }
  DENOM = *V - MINIM;
  if (*V != 0 && DENOM !=0)
    {
    *S = ((DENOM << 16) / *V) - 1;
    }
  else
    *S = 0;/* Color is black! and Sat has no meaning */
  if(*S == 0) /*  R=G=B => shade of grey and Hue has no meaning */
    {
    *H = 0;
    *V = *V << 8;
    return(1);  /* v or s or both are 0 */
    }
  if (*V == MINIM)
    {
    *H = 0;
    *V = *V << 8;
    return(0);
    }
  R1 = (((*V - R) * 60) << 6) / DENOM;    /* distance of color from red  */
  G1 = (((*V - G) * 60) << 6) / DENOM;    /* distance of color from green */
  B1 = (((*V - B) * 60) << 6) / DENOM;    /* distance of color from blue  */
  if(*V == R)
    if (MINIM == G)
      *H = (300 << 6) + B1;
    else      *H = (60 << 6) - G1;
  if(*V == G)
    if (MINIM == B)
      *H = (60 << 6) + R1;
    else
      *H = (180 << 6) - B1;
  if(*V == B)
    if(MINIM == R)
      *H = (180 << 6) + G1;
    else
      *H = (300 << 6) - R1;

  *V = *V << 8;
  return(0);
}

static int H_R (R, G, B, H, S, V)
unsigned char *R, *G, *B;
unsigned long  H, S, V;
{
  unsigned long P1, P2, P3;
  int  RMD, I;

  if(H >= 23040)
    H = H % 23040; /*  Makes h circular  */
  I = H / 3840;
  RMD = H % 3840;  /*  RMD = fractional part of H  */

  P1 = ((V * (65535 - S)) / 65280) >> 8;
  P2 = (((V * (65535 - (S * RMD) / 3840)) / 65280) - 1) >> 8;
  P3 = (((V * (65535 - (S * (3840 - RMD)) / 3840)) / 65280)) >> 8;
  V = V >> 8;
  switch (I)
    {
    case 0:
      *R = V;
      *G = P3;
      *B = P1;
    break;
    case 1:
      *R = P2;
      *G = V;
      *B = P1;
    break;
    case 2:
      *R = P1;
      *G = V;
      *B = P3;
    break;
    case 3:
      *R = P1;
      *G = P2;
      *B = V;
    break;
    case 4:
      *R = P3;
      *G = P1;
      *B = V;
    break;
    case 5:
      *R = V ;
      *G = P1;
      *B = P2;
    break;
    }
  return(0);
}

/***************************************************************************/
/*                                                                         */
/* EB & DG fiddled with outputs for Rayshade so they work. with v4.x.      */
/* EB == eli brandt.   ebrandt@jarthur.claremont.edu                       */
/* DG == dan goldwater.  daniel_goldwater@brown.edu & dgold@math.umass.edu */
/*  (NOTE: all the stuff we fiddled with is commented with "EB & DG"       */
/*  (plus even some extra bonus comments!)                                 */
/* general raytracing code info/notes:                                     */
/*                                                                         */
/*  ray == 0 means no raytracer output  ray == 7 is for dxf                */
/*  ray == 1 is for dkb/pov             ray == 4 is for mtv                */
/*  ray == 2 is for vivid               ray == 5 is for rayshade           */
/*  ray == 3 is for raw                 ray == 6 is for acrospin           */
/*                                                                         */
/*  rayshade needs counterclockwise triangles.  raytracers that support    */
/*  the 'heightfield' primitive include rayshade and pov.                  */
/*  anyone want to write code to make heightfields?  they are *MUCH*       */
/*  faster to trace than triangles when doing landscapes...                */
/*                                                                         */
/*  stuff EB & DG changed:                                                 */
/*  made the rayshade output create a "grid" aggregate object              */
/*  (one of rayshade's primitives), instead of a global grid.              */
/*  As a result, the grid can be optimized based on the number of          */
/*  triangles.                                                             */
/*  the z component of the grid can always be 1 since the surface formed   */
/*  by the triangles is flat (ie, it doesnt curve over itself).            */
/*  This is a major optimization.  the x and y grid size is                */
/*  also optimized for a 4:3 aspect ratio image, to get the fewest         */
/*  possible traingles in each grid square.                                */
/*  also, we fixed the rayshade code so it actually produces output that   */
/*  works with rayshade. (maybe the old code was for a really old version  */
/*  of rayshade?).                                                         */
/***************************************************************************/

/********************************************************************/
/*                                                                  */
/*  This routine writes a header to a ray tracer data file. It      */
/*  Identifies the version of xmfract which created it an the      */
/*  key 3D parameters in effect at the time.                        */
/*                                                                  */
/********************************************************************/

static char declare[] = {"DECLARE  "};
static char frac_default[] = {"F_Dflt"};
static char c_color[] = {"COLOR  "};
static char dflt[] = {"RED 0.8 GREEN 0.4 BLUE 0.1\n"};
static char d_color[] = {"0.8 0.4 0.1"};
static char r_surf[] = {"0.95 0.05 5 0 0\n"};
static char surf[] = {"surf={diff="};
static char rs_surf[] = {"applysurf diffuse "};   /* EB & DG: changed "surface T" to "applysurf" and "diff" to "diffuse" */
static char end[] = {"END_"};
static char plane[] = {"PLANE"};
static char m1[] = {"-1.0 "};
static char one[] = {" 1.0 "};
static char z[]  = {" 0.0 "};
static char bnd_by[]  = {" BOUNDED_BY\n"};
static char end_bnd[]  = {" END_BOUND\n"};
static char inter[]  = {"INTERSECTION\n"};
static char fmt[] = "   %s <%s%s%s> % #4.3f %s%s\n";
static char dxf_begin[] = {"  0\nSECTION\n  2\nTABLES\n  0\nTABLE\n  2\nLAYER\n\
 70\n   2\n  0\nLAYER\n  2\n0\n 70\n   0\n 62\n   7\n  6\nCONTINUOUS\n\
  0\nLAYER\n  2\nFRACTAL\n 70\n  64\n 62\n   1\n  6\nCONTINUOUS\n  0\n\
ENDTAB\n  0\nENDSEC\n  0\nSECTION\n  2\nENTITIES\n"};
static char dxf_3dface[] = {"  0\n3DFACE\n  8\nFRACTAL\n 62\n%3d\n"};
static char dxf_vertex[] = {"%3d\n%g\n"};
static char dxf_end[] = {"  0\nENDSEC\n  0\nEOF\n"};

static char composite[] = {"COMPOSITE"};
static char object[]  = {"OBJECT"};
static char triangle[] = {"TRIANGLE "};
static char l_tri[] = {"triangle"};
static char texture[] = {"TEXTURE\n"};
/* static char end_texture[] = {" END_TEXTURE\n"}; */
static char red[] = {"RED"};
static char green[] = {"GREEN"};
static char blue[] = {"BLUE"};

static char frac_texture[] = {"  AMBIENT 0.25 DIFFUSE 0.75"};

static char polygon[] = {"polygon={points=3;"};
static char vertex[] = {" vertex =  "};
static char d_vert[] = {"  <"};
static char f1[] = "% #4.4f ";
/* EB & DG: changed this to much better values. now it actually makes a good trace */
static char grid[] = {"screen 640 480\neyep 0 2.1 0.8\nlookp 0 0 -0.95\nlight 1 point -2 1 1.5\n"};
static char grid2[] = {"background .3 0 0\nreport verbose\n"};

static char n[] = "\n";
static char f2[] = "R%dC%d R%dC%d\n";
static char ray_comment1[] = {"/* make a gridded aggregate. this size grid is fast for landscapes. */\n"};
static char ray_comment2[] = {"/* make z grid = 1 always for landscapes. */\n\n"};
static char grid3[] = {"grid 33 25 1\n"};


static int RAY_Header(void)
{  /* Open the ray tracing output file */
  check_writefile(ray_name,".ray");
  if ((File_Ptr1=fopen(ray_name,"w"))==NULL)
    return(-1); /* Oops, somethings wrong! */

  if (RAY == 2)
    fprintf(File_Ptr1, "//");
  if (RAY == 4)
    fprintf(File_Ptr1, "#");
  if (RAY == 5)
    fprintf(File_Ptr1, "/*\n");
  if (RAY == 6)
    fprintf(File_Ptr1, "--");
    if (RAY == 7)
      fprintf(File_Ptr1, dxf_begin);

    if (RAY != 7)
     fprintf(File_Ptr1, banner, s3, release/100., s3a);

  if (RAY == 5)
    fprintf(File_Ptr1, "*/\n");


  /* Set the default color */
  if (RAY == 1)
    {
    fprintf (File_Ptr1, fs, declare, frac_default);
    fprintf (File_Ptr1, " = ");
    fprintf (File_Ptr1, fs, c_color, dflt);
    }
  if (BRIEF)
    {
    if (RAY == 2)
      {
      fprintf (File_Ptr1, fs, surf, d_color);
      fprintf (File_Ptr1, ";}\n");
      }
    if (RAY == 4)
      {
      fprintf (File_Ptr1, "f ");
      fprintf (File_Ptr1, fs, d_color, r_surf);
      }
    if (RAY == 5)
      fprintf (File_Ptr1, fs, rs_surf, d_color);
    }
  if (RAY != 7)
    fprintf (File_Ptr1, n);

  if (RAY == 5)       /* EB & DG: open "grid" opject, a speedy way to do aggregates in rayshade */
    fprintf (File_Ptr1, fff, ray_comment1,ray_comment2,grid3);

  if (RAY == 6)
    fprintf (File_Ptr1, "%s", acro_s1);

  return(0);
}

/********************************************************************/
/*                                  */
/*  This routine describes the triangle to the ray tracer, it    */
/*  sets the color of the triangle to the average of the color    */
/*  of its verticies and sets the light parameters to arbitrary    */
/*  values.                              */
/*                                  */
/*  Note: numcolors (number of colors in the source          */
/*  file) is used instead of colors (number of colors avail. with  */
/*  display) so you can generate ray trace files with your LCD    */
/*  or monochrome display                      */
/*                                  */
/********************************************************************/

static int out_triangle(struct f_point pt1, struct f_point pt2,
      struct f_point pt3, int c1, int c2, int c3)
{
int i, j;
float c[3];
float pt_t[3][3];

  /* Normalize each vertex to screen size and adjust coordinate system */
  pt_t[0][0]      =  2 * pt1.x      / xdots  - 1;
  pt_t[0][1]      =  (2 * pt1.y     / ydots  - 1);
  pt_t[0][2]      =  -2 * pt1.color / numcolors - 1;
  pt_t[1][0]      =  2 * pt2.x      / xdots  - 1;
  pt_t[1][1]      =  (2 * pt2.y     / ydots  - 1);
  pt_t[1][2]      =  -2 * pt2.color / numcolors - 1;
  pt_t[2][0]      =  2 * pt3.x      / xdots  - 1;
  pt_t[2][1]      =  (2 * pt3.y     / ydots  - 1);
  pt_t[2][2]      =  -2 * pt3.color / numcolors - 1;

  /* Color of triangle is average of colors of its verticies */
  if (!BRIEF)
    for (i=0;i<=2;i++)
      c[i] = (float)((float)(dacbox[c1][i]) +
                     (float)(dacbox[c2][i]) +
                     (float)(dacbox[c3][i]) /
                     (float)(3 * 63));

    /* get rid of degenerate triangles: any two points equal */
    if ((pt_t[0][0] == pt_t[1][0] &&
         pt_t[0][1] == pt_t[1][1] &&
         pt_t[0][2] == pt_t[1][2]) ||

        (pt_t[0][0] == pt_t[2][0] &&
         pt_t[0][1] == pt_t[2][1] &&
         pt_t[0][2] == pt_t[2][2]) ||

        (pt_t[2][0] == pt_t[1][0] &&
         pt_t[2][1] == pt_t[1][1] &&
         pt_t[2][2] == pt_t[1][2]))
      return(0);

  /* Describe the triangle */
  if (RAY == 1)
    fprintf (File_Ptr1, " %s\n  %s", object, triangle);
    if (RAY == 2 && !BRIEF)
    fprintf (File_Ptr1, "%s", surf);
  if (RAY == 4 && !BRIEF)
    fprintf (File_Ptr1, "f");
  if (RAY == 5 && !BRIEF)
    fprintf (File_Ptr1, "%s", rs_surf);

  if (!BRIEF && RAY != 1 && RAY != 7)
    for (i=0;i<=2;i++)
    fprintf (File_Ptr1, f1, c[i]);

  if (RAY == 2)
    {
    if (!BRIEF)
      fprintf (File_Ptr1, ";}\n");
    fprintf (File_Ptr1, "%s", polygon);
    }
  if (RAY == 4)
    {
    if (!BRIEF)
      fprintf (File_Ptr1, "%s", r_surf);
    fprintf (File_Ptr1, "p 3");
    }
  if (RAY == 5)
    {
    if (!BRIEF)
      fprintf (File_Ptr1, n);
    fprintf (File_Ptr1, "%s", l_tri);
    /* EB & DG: removed "T" after "triangle" */
    }

    if (RAY == 7)
      fprintf (File_Ptr1, dxf_3dface, min(255, max(1, c1)));

  for(i=0;i<=2;i++)  /*  Describe each  Vertex  */
    {
    if (RAY != 7)
      fprintf (File_Ptr1, n);
    if (RAY == 1)
      fprintf (File_Ptr1, "%s", d_vert);
    if (RAY == 2)
      fprintf (File_Ptr1, "%s", vertex);
    if (RAY > 3 && RAY != 7)
      fprintf (File_Ptr1, " ");

    for(j=0;j<=2;j++)
      {
      if (RAY == 7)
        {
        /* write 3dface entity to dxf file */
        fprintf (File_Ptr1, dxf_vertex, 10*(j+1)+i, pt_t[i][j]);
        if (i == 2)        /* 3dface needs 4 vertecies */
          fprintf (File_Ptr1, dxf_vertex, 10*(j+1)+i+1, pt_t[i][j]);
        }
      else if (!(RAY == 4 || RAY == 5))
        fprintf (File_Ptr1, f1, pt_t[i][j]); /* Right handed */
      else
        fprintf (File_Ptr1, f1, pt_t[2-i][j]); /* Left handed */
      }

    if (RAY == 1)
      fprintf (File_Ptr1, ">");
    if (RAY == 2)
      fprintf (File_Ptr1, ";");
    }

  if (RAY == 1)
    {
    fprintf (File_Ptr1, " %s%s\n", end, triangle);
    if (!BRIEF)
      {
      fprintf (File_Ptr1,
        "  %s   %s%s% #4.4f %s% #4.4f %s% #4.4f\n%s %s%s",
        texture,
        c_color, red, c[0], green, c[1], blue, c[2],
        frac_texture,
        end, texture);
      }
    fprintf (File_Ptr1, "  %s%s  %s%s",
      c_color, frac_default,
      end, object);
      triangle_bounds(pt_t); /* update bounding info */
    }
  if (RAY == 2)
    fprintf (File_Ptr1, "}");
  if (RAY == 3 && !BRIEF)
    fprintf (File_Ptr1, n);

  if (RAY != 7)
    fprintf (File_Ptr1, n);

  return(0);
}

/********************************************************************/
/*                                                                  */
/*  This routine calculates the min and max values of a triangle    */
/*  for use in creating ray tracer data files. The values of min    */
/*  and max x, y, and z are assumed to be global.                   */
/*                                                                  */
/********************************************************************/

static void triangle_bounds(float pt_t[3][3])

{
int i,j;

  for (i=0;i<=2;i++)
  for (j=0;j<=2;j++)
    {
    if(pt_t[i][j] < min_xyz[j]) min_xyz[j] = pt_t[i][j];
    if(pt_t[i][j] > max_xyz[j]) max_xyz[j] = pt_t[i][j];
    }
  return;
}

/********************************************************************/
/*                                                                  */
/*  This routine starts a composite object for ray trace data files.*/
/*                                                                  */
/********************************************************************/

static int start_object(void)
{
  if (RAY != 1)
    return(0);

  /*  Reset the min/max values, for bounding box  */
  min_xyz[0] = min_xyz[1] = min_xyz[2] =  999999;
  max_xyz[0] = max_xyz[1] = max_xyz[2] = -999999;

  fprintf (File_Ptr1, "%s\n", composite);
  return(0);
}

/********************************************************************/
/*                                                                  */
/*  This routine adds a bounding box for the triangles drawn        */
/*  in the last block and completes the composite object created.   */
/*  It uses the globals min and max x,y and z calculated in         */
/*  z calculated in Triangle_Bounds().                              */
/*                                                                  */
/********************************************************************/

static int end_object(int triout)
{
  if (RAY == 7)
    return(0);
  if (RAY == 1)
    {
    if (triout)
      {
      /* Make sure the bounding box is slightly larger than the object */
      int i;
      for (i=0;i<=2;i++)
        {
        if (min_xyz[i] == max_xyz[i])
          {
          min_xyz[i] -= 0.01;
          max_xyz[i] += 0.01;
          }
        else
          {
          min_xyz[i] -= (max_xyz[i] - min_xyz[i]) * 0.01;
          max_xyz[i] += (max_xyz[i] - min_xyz[i]) * 0.01;
          }
        }

      /* Add the bounding box info */
      fprintf (File_Ptr1, "%s  %s", bnd_by, inter);
      fprintf (File_Ptr1, fmt, plane,m1,z,z,-min_xyz[0],end,plane);
      fprintf (File_Ptr1, fmt, plane,one,z,z,max_xyz[0],end,plane);
      fprintf (File_Ptr1, fmt, plane,z,m1,z,-min_xyz[1],end,plane);
      fprintf (File_Ptr1, fmt, plane,z,one,z,max_xyz[1],end,plane);
      fprintf (File_Ptr1, fmt, plane,z,z,m1,-min_xyz[2],end,plane);
      fprintf (File_Ptr1, fmt, plane,z,z,one,max_xyz[2],end,plane);
      fprintf (File_Ptr1, "  %s%s%s", end, inter, end_bnd);
      }

    /* Complete the composite object statement */
    fprintf (File_Ptr1, "%s%s\n", end, composite);
    }

  if (RAY!=6 && RAY!=5)
    fprintf (File_Ptr1, n);  /* EB & DG: too many newlines */

  return(0);
}

static void line3d_cleanup()
{
 int i,j;
  if(RAY && File_Ptr1)
    {  /*  Finish up the ray tracing files */
    static char n_ta[] ={"{ No. Of Triangles = "};
    if (RAY != 5 && RAY != 7)
      fprintf (File_Ptr1, n);  /* EB & DG: too many newlines */
    if (RAY == 2)
      fprintf(File_Ptr1, "\n\n//");
    if (RAY == 4)
      fprintf(File_Ptr1, "\n\n#");

    if (RAY == 5)
      /* EB & DG: end grid aggregate */
      fprintf (File_Ptr1, "end\n\n/*good landscape:*/\n%s%s\n/*", grid,grid2);

    if (RAY == 6)
      {
      fprintf (File_Ptr1, "%s", acro_s2);
      for (i=0;i<RO;i++)
        for (j=0;j<=CO_MAX;j++)
          {
          if (j < CO_MAX)
            fprintf (File_Ptr1, f2, i, j, i, j+1);
          if (i < RO - 1)
            fprintf (File_Ptr1, f2, i, j, i+1, j);
          if (i && i < RO && j < CO_MAX)
            fprintf (File_Ptr1, f2, i, j, i-1, j+1);
          }
      fprintf (File_Ptr1, "\n\n--");
      }
    if (RAY != 7)
      fprintf(File_Ptr1, "%s%d }*/\n\n",n_ta, (int)num_tris);
      /* EB & DG: this is what s3a should be */
      /* see note    fprintf(File_Ptr1, "%s%d%s",n_ta, num_tris, s3a);
         BUG! s3a=null here! why?! */
    if (RAY == 7)
      fprintf (File_Ptr1, dxf_end);
    fclose(File_Ptr1);
    File_Ptr1 = NULL;
    }

  if (Targa_Out)
    {  /*  Finish up targa files */
    T_header_24 = 18; /* Reset Targa header size */
    enddisk();
    if (!debugflag && T_Safe && !xmerror && Targa_Overlay)
      {
      remove(light_name);
      rename(targa_temp,light_name);
      }
    if (!debugflag && Targa_Overlay)
      remove (targa_temp);
    }
  usr_floatflag &= 1; /* strip second bit */
  xmerror = T_Safe = 0;
}

/*******************************************************************
 * MODIFICATION HISTORY
 *
 * $Log: line3d.c,v $
 * Revision 1.400  1995/06/20  15:39:23  darryl
 * Revision sync - release 1.4
 *
 * Revision 1.4  1995/03/20  04:01:03  darryl
 * Revision sync. Change all BulletinBoard dialogs to Forms.
 * Delete all specific widget positioning and sizing resources.
 * Change popup menus to option menus. Add Undo command to
 * the color editor. Change usepixtab to 1 if privatecolor.
 *
 * Revision 1.1  1995/03/03  22:56:36  darryl
 * Linux and HPUX enhancements, bug fixes. New basic options
 * dialog, 'Flip' submenu created differently. EditRes protocol
 * event handlers conditionally included. xmconfig.h generalized.
 *
 * Revision 0.301  1995/01/10  00:40:37  darryl
 * Add comments, delete references to obsolete code.
 *
 * Revision 0.300  1994/12/16  18:58:09  darryl
 * Revision sync - beta3 version.
 *
 * Revision 0.200  1994/12/02  02:54:24  darryl
 * Revision sync - beta2 version.
 *
 * Revision 0.103  1994/11/30  00:58:52  darryl
 * Remove magic numbers from calc_status, use values
 * defined in the main header file.
 *
 * Revision 0.102  1994/11/29  07:30:29  darryl
 * Make the 'Create Image' button label dynamic. Depending on
 * the value of calc_status, it reads 'Create Image' or
 * 'Continue calculation'. The accelerator remains the same.
 *
 * Revision 0.101  1994/11/28  20:21:53  darryl
 * Fix bug where some non-standard fractal types were not
 * interrupted when escape key was pressed. Move more macros
 * and definitions to header files.
 *
 * Revision 0.100  1994/11/18  05:31:10  darryl
 * Initial beta version.
 *
 * END MODIFICATION HISTORY
 *******************************************************************/
