
/*******************************************************\
* 							*
*              @(#)graph3d.c	1/2/89			*
*	Author:       Tony Plate			*
*	Copyright (C) 1989 Tony Plate			*
*	This program may be freely distributed and	*
*	used, provided that this copyright notice is	*
*	retained intact.  There is no warranty with	*
*	this software.					*
* 							*
\*******************************************************/

#include <stdio.h>
#include <math.h>
#include <ctype.h>
#include <stdlib.h>

#ifdef HAVE_X
#include <setjmp.h>
#endif /* HAVE_X */
#include "types.h"

#include "g3dproto.h"

char *options_description[] = {
"Options: (an unambiguous substring is sufficient)",
"-2d		: just work in 2 dimensions, like graph(1)",
"-3d		: work in 3 dimensions (default)",
"-axis		: draw an axis on the graph",
"-back		: place the origin at the back",
"-border		: put a square border around the graph",
"-bounds		: print the bounds in the top left corner",
"-box		: put a cubic box around the graph",
"-focus	D	: put the focus at distance D from the centre of the graph",
"-forcetty	: write to the tty even if we don't own it",
"-front		: place the origin at the front",
"-graph		: produce output for graph(1)",
"-help		: print this list",
"-hide		: do hidden line removal by Z-sorting polygons",
"-label	X Y string	: put a label at offsets X and Y",
"-left		: place the origin at the left",
"-light_source x y z	: place the light source in direction x y z",
"-noerase	: don't erase the previous drawing, only applies when",
"		  the -plot option is selected",
"-noinvisibleborder	: don't delimit the graph with an invisible border",
"-orient		: draw a small labelled axis in the right hand corner",
"-plane	D	: put the projection plane at distance D from the center",
"		  of the graph",
"-plot		: produce output in plot(5) format",
"-plot-scale S	: make the scale for plot output equal to S",
"-ps		: produce POSTSCRIPT output",
"-ps-scale S	: make the scale for POSTSCRIPT output equal to S",
"-q1		: put the graph in the first quadrant",
"-q2		: put the graph in the second quadrant",
"-q3		: put the graph in the third quadrant",
"-q4		: put the graph in the fourth quadrant",
"-right		: place the origin at the right",
"-show-rotations	: show the rotations that would be used and",
"		  exit without drawing any graph",
"-stdout		: send output to stdout",
"-tek		: same as -plot",
"-tty <hostname>:X	: send tektronics output directly to <hostname>:/dev/ttyX",
"-xfraction F	: use fraction 0 <= F <=1 of the X dimension of the viewport",
"-xlow X		: start using the X viewport at 0 <= X <= 1",
"-xrotation R	: rotate the graph in the X plane by R degrees",
"-xscale S	: scale the graph in the X direction by S",
"-yfraction F	: use fraction 0 <= F <=1 of the Y dimension of the viewport",
"-ylow Y		: start using the Y viewport at 0 <= Y <= 1",
"-yrotation R	: rotate the graph in the Y plane by R degrees",
"-yscale S	: scale the graph in the Y direction by S",
"-zrotation R	: rotate the graph in the Z plane by R degrees",
"-zscale S	: scale the graph in the Z direction by S",

#ifdef HAVE_X
"-X             : produce output for an X-windows server",
"-geometry G    : use geometry G in X",
"-font F        : use font F in X",
#endif /* HAVE_X */

  NULL
};

#ifndef MAXFLOAT
#define MAXFLOAT (float)1e37
#endif

#define DB(X)
#define DBDATA(X)
/*
 * This program works as follows:
 * (1) Read command line options
 * (2) Read data - one point per line, & knows format from first line
 * (3) Calculate size of image & the translation needed to put the centre
 *     of it at the origin.
 * (4) Add axis or a box to the list of polygons if they were requested.
 * (5) Create a cube & transform it into the viewing coordinates, in
 *     order to calculate the x & y scale factors neccessary to put
 *     the image in the viewing rectangle.
 * (6) Make the transformations on the list of polygons, perspective is done
 *     by translating the object so that its centre is at the origin, and
 *     so that its longest dimension is one.
 *     Then it is rotated so that the angle of view is parallel with
 *     the z-axis, and the points are scaled by their position on the z-axis.
 * (7) Convert the list of polygons to a vector of polygons (for sorting)
 * (8) If hidden line removal is desired, Qsort the vector by z-position
 * (9) Print out the polygons in the desired format
 */


char *my_name;
int dont_change_out=0;
char *use_tty=NULL;
FILE *out;
int force_tty = 0;


#ifdef HAVE_X
extern char *geom, *fontName; /* for X, MB */
#endif /* HAVE_X */

/* globals for redraw */
STYLE     Style;
TRANSFORM Transform;

#ifdef HAVE_X
jmp_buf   Redraw_env, Newview_env;
#endif /* HAVE_X */

#define DEG_TO_RAD(x) ((x) * 3.141592 / 180)
#define MIN(x,y) ( (x) < (y) ? (x) : (y) )
#define set_min_max(x,min,max) {real internal_temp=(x); if (min>max) min=max=internal_temp; else if (internal_temp<min) min=internal_temp; else if (internal_temp>max) max=internal_temp;}


int main(int argc ,
         char *argv[] )

{
/* As we are going to setjmp/longjmp into main, lets make everything static */

  static POLYGON *poly_list,**poly_vec,*label_list,*axis_list;
  static int n_polys;
  static MIN_MAX min_max;

  my_name = argv[0];
  out = stdout;
  poly_list = label_list = axis_list = NULL;
  read_options(argv,&Transform,&Style,&label_list);

#ifdef HAVE_PLOT
  if (Style.output_style==PLOT) CheckTekTerminal();
#endif /* HAVE_PLOT  */

  output_prologue(&Transform,&Style);

  /* read the input */
  n_polys = read_polys(&Style,&poly_list,&label_list,stdin);

  /* find the min & max on all dim. */
  find_min_max_world(poly_list,&min_max);
  if (Style.bounds) add_bounds_label(&min_max,&label_list);

  /* add axis or box to the polygons */
  add_axis(&axis_list,&min_max,&Style);

  DB( show_poly_list_world(poly_list); )
  DB( show_min_max(&min_max); )
  
#ifdef HAVE_X
  /* save environment for display of new view */
  setjmp(Newview_env);
#endif /* HAVE_X   */

  calc_view_scale(&Transform,&min_max,&Style);
  
  DB( show_poly_list_world(poly_list); )

  transform_poly_list(poly_list,&Transform);
  transform_poly_list(axis_list,&Transform);
  poly_vec = list_to_vec(poly_list,n_polys);

  DB( printf("Label list:\n");  show_poly_list_world(label_list); )
  DB( show_poly_vec_world(n_polys,poly_vec); )

  if (Style.remove_hidden)
    qsort(poly_vec,(size_t)n_polys,sizeof(POLYGON*),compare_poly);

  DB( show_poly_vec_world(n_polys,poly_vec); )
  DB( show_poly_vec_view(n_polys,poly_vec); )

#ifdef HAVE_X
  /* save environment for redraw */
  setjmp(Redraw_env);
#endif /* HAVE_X */

  /* output the axis list before the polygons */
  output_back_axis(axis_list,&Style);
  output_poly_vec(n_polys,poly_vec,&Style);
  /* and now output the axis that should be in front */
  output_front_axis(axis_list,&Style);
  output_label_list(label_list,&Transform,&Style);
  if (Style.orient) output_orient(&Transform,&Style);
  output_epilogue(&Style);
}

int read_options(char *argv[] ,
                 TRANSFORM *t ,
                 STYLE *s ,
                 POLYGON **label_list )

{
  char *p;
  int show_rotations=0;
  float sqrt3 = sqrt((double)3);
  /* set the rotations to put the origin in the left hand corner */
  t->proj_plane = 1.0;
  t->proj_focus = 1.0;
  t->x_low	= 0.0;
  t->x_frac	= 1.0;
  t->x_rot	= -65;
  t->x_scale	= 1.0;
  t->y_low	= 0.0;
  t->y_frac	= 1.0;
  t->y_rot	= 0;
  t->y_scale	= 1.0;
  t->z_rot	= 30;
  t->z_scale	= 1.0;
  s->axis_style = NONE;
  s->output_style = POST_SCRIPT;
  s->light_source.x = -1.0 / sqrt3;
  s->light_source.y = -1.0 / sqrt3;
  s->light_source.z = 1.0 / sqrt3;




#ifdef HAVE_PLOT
  s->output_style = PLOT;
#endif /* HAVE_PLOT */

#ifdef HAVE_X
  s->output_style = X;
#endif /* HAVE_X */

  s->scale_style = FIRM;
  s->dimension	= D3;
  s->remove_hidden = 0;
  s->ps_scale	= 10000;
  s->plot_scale	= 3120; /* just right for tektronics 4014 */
  s->erase	= 1;
  s->border	= 0;
  s->bounds	= 0;
  s->invisible_border	= 1;
  s->orient	= 0;
  while (p = *(++argv)) {
    if (*p == '-') {
      p++;
      if (prefix(p,"2d")) {
	s->dimension = D2;
	t->x_rot = 0;
	t->y_rot = 0;
	t->z_rot = 0;
      } else if (prefix(p,"3d")) {
	s->dimension = D3;
      } else if (prefix(p,"axis")) {
	s->axis_style = AXIS;
      } else if (prefix(p,"back")) {
	t->x_rot = -65;
	t->y_rot = 0;
	t->z_rot = 150;
      } else if (prefix(p,"border")) {
	s->border = 1;
      } else if (prefix(p,"bounds")) {
	s->bounds = 1;
      } else if (prefix(p,"box")) {
	s->axis_style = BOX;
      } else if (prefix(p,"forcetty")) {
	force_tty = 1;
      } else if (prefix(p,"focus")) {
	if (!*(++argv)) {
	  fprintf(stderr,"%s: Expected distance after '-focus'\n",my_name);
	  exit(1);
	}
	t->proj_focus = atof(*argv);
      } else if (prefix(p,"front")) {
	t->x_rot = -65;
	t->y_rot = 0;
	t->z_rot = -30;
      } else if (prefix(p,"graph")) {
	s->output_style = GRAPH;
      } else if (prefix(p,"help")) {
	print_options();
	exit(0);
      } else if (prefix(p,"hide")) {
	s->remove_hidden = 1;
      } else if (prefix(p,"label")) {
	double x,y;
	if (!argv[1] || !argv[2] || !argv[3])
	  error("expected 3 more arguments with '-%s'",p);
	x = atof(argv[1]);
	y = atof(argv[2]);
	add_poly(label_list,new_point(NULL,x,y,0.0),NULL,argv[3],0);
	argv += 3;
      } else if (prefix(p,"left")) {
	t->x_rot = -65;
	t->y_rot = 0;
	t->z_rot = 30;
      } else if (prefix(p, "light_source")) {
	float x, y, z, length;
	if (!argv[1] || !argv[2] || !argv[3])
	  error("expected 3 more arguments with '-%s'",p);
	x = atof(*(++argv));
	y = atof(*(++argv));
	z = atof(*(++argv));
	/* normalize light_source vector*/
	length = sqrt((double)(x * x + y * y + z * z));
	if(length == 0)
	  error("%s must have at least one non-zero component", p);
	else {
	  s->light_source.x = x / length;
	  s->light_source.y = y / length;
	  s->light_source.z = z / length;
	}
      } else if (prefix(p,"noerase")) {
	s->erase = 0;
      } else if (prefix(p,"noinvisibleborder")) {
	s->invisible_border = 0;
      } else if (prefix(p,"orient")) {
	s->orient = 1;
      } else if (prefix(p,"plane")) {
	if (!*(++argv)) error("Expected distance after '-%s'",p);
	t->proj_plane = atof(*argv);
      } else if (prefix(p,"plot")) {
	s->output_style = PLOT;
      } else if (prefix(p,"plot-scale")) {
	s->output_style = PLOT;
	if (!*(++argv)) error("Expected scale factor after '-%s'",p);
	s->plot_scale = atoi(*argv);
      } else if (prefix(p,"ps")) {
	s->output_style = POST_SCRIPT;
      } else if (prefix(p,"ps-scale")) {
	s->output_style = POST_SCRIPT;
	if (!*(++argv)) error("Expected scale factor after '-%s'",p);
	s->ps_scale = atoi(*argv);
      } else if (prefix(p,"q1")) {
	t->x_frac = 0.5;
	t->y_frac = 0.5;
	t->x_low = 0.0;
	t->y_low = 0.5;
      } else if (prefix(p,"q2")) {
	t->x_frac = 0.5;
	t->y_frac = 0.5;
	t->x_low = 0.5;
	t->y_low = 0.5;
      } else if (prefix(p,"q3")) {
	t->x_frac = 0.5;
	t->y_frac = 0.5;
	t->x_low = 0.0;
	t->y_low = 0.0;
      } else if (prefix(p,"q4")) {
	t->x_frac = 0.5;
	t->y_frac = 0.5;
	t->x_low = 0.5;
	t->y_low = 0.0;
      } else if (prefix(p,"right")) {
	t->x_rot = -65;
	t->y_rot = 0;
	t->z_rot = -150;
      } else if (prefix(p,"show-rotations")) {
	show_rotations = 1;
      } else if (prefix(p,"stdout")) {
	dont_change_out=1;
      } else if (prefix(p,"tty")) {
	if (!*(++argv)) error("Expected tty name after \"%s\"",p);
	use_tty = *argv;
      } else if (prefix(p,"tek")) {
	s->output_style = PLOT;
      } else if (prefix(p,"xfraction")) {
	if (!*(++argv)) error("Expected value after '-%s'",p);
	t->x_frac = atof(*argv);
      } else if (prefix(p,"xlow")) {
	if (!*(++argv)) error("Expected value after '-%s'",p);
	t->x_low = atof(*argv);
      } else if (prefix(p,"xrotation")) {
	if (!*(++argv)) error("Expected angle after '-%s'",p);
	t->x_rot = atof(*argv);
      } else if (prefix(p,"xscale")) {
	if (!*(++argv)) error("Expected scale factor after '-%s'",p);
	t->x_scale = atof(*argv);
      } else if (prefix(p,"yfraction")) {
	if (!*(++argv)) error("Expected value after '-%s'",p);
	t->y_frac = atof(*argv);
      } else if (prefix(p,"ylow")) {
	if (!*(++argv)) error("Expected value after '-%s'",p);
	t->y_low = atof(*argv);
      } else if (prefix(p,"yrotation")) {
	if (!*(++argv)) error("Expected angle after '-%s'",p);
	t->y_rot = atof(*argv);
      } else if (prefix(p,"yscale")) {
	if (!*(++argv)) error("Expected scale factor after '-%s'",p);
	t->y_scale = atof(*argv);
      } else if (prefix(p,"zrotation")) {
	if (!*(++argv)) error("Expected angle after '-%s'",p);
	t->z_rot = atof(*argv);
      } else if (prefix(p,"zscale")) {
	if (!*(++argv)) error("Expected scale factor after '-%s'",p);
	t->z_scale = atof(*argv);

#ifdef HAVE_X
      } else if (prefix(p, "X")) {
	s->output_style = X;
      } else if (prefix(p, "geometry")) {
	if (!*(++argv)) error("Expected geometry after '-%s'",p);
	geom = *argv;
      } else if (prefix(p, "font")) {
	if (!*(++argv)) error("Expected font name after '-%s'",p);
	fontName = *argv;
#endif /* HAVE_X */

      } else {
	goto bad_arg;
      }
    } else {
      bad_arg:
        error("Bad argument: '%s'",p);
    }
  }
  switch (s->output_style) {
  case POST_SCRIPT:
    s->x_char_size = 0.01;
    s->y_char_size = 1.0;
    break;
#ifdef HAVE_PLOT
  case PLOT:
    s->x_char_size = 0.014;
    s->y_char_size = 0.025;
    break;
#endif /* HAVE_PLOT */
  case GRAPH:
    s->x_char_size = 0.01;
    s->y_char_size = 0.013;
    break;
#ifdef HAVE_X
  case X:
    /* do nothing; font sizes are determined during xopenpl() */
    break;
#endif /* HAVE_X */
  default:
    bad_style(s);
    exit(1);
  }

/* means no perspective
  if (t->proj_plane == 0) {
    fprintf(stderr,"%s: cannot have projection plane and focus in the same position\n",my_name);
    exit(1);
  }
*/
  if ((t->x_frac + t->x_low > 1) || (t->y_frac + t->y_low > 1)) {
    fprintf(stderr,
	    "%s: bad combination of -h & -u or -w & -r values\n",my_name);
    exit(1);
  }
  if (t->y_frac<0 || t->y_frac>1) {
    fprintf(stderr,"%s: bad -h value (Y_FRAC)\n",my_name);
    exit(1);
  }
  if (t->x_frac<0 || t->x_frac>1) {
    fprintf(stderr,"%s: bad -w value (X_FRAC)\n",my_name);
    exit(1);
  }
  if (t->y_low<0 || t->y_low>1) {
    fprintf(stderr,"%s: bad -u value (Y_LOW)\n",my_name);
    exit(1);
  }
  if (t->x_low<0 || t->x_low>1) {
    fprintf(stderr,"%s: bad -r value (X_LOW)\n",my_name);
    exit(1);
  }
  if (show_rotations) {
    printf("-rx		%g	x rotation (degrees)\n",t->x_rot);
    printf("-ry		%g	y rotation (degrees)\n",t->y_rot);
    printf("-rz		%g	z rotation (degrees)\n",t->z_rot);
    exit(0);
  }
}

char *malloc_fail(int s )

{
  char *p;
  p = malloc((unsigned)s);
  if (p==NULL) {
    fprintf(stderr,"%s: out of memory (requested %d)\n",my_name,s);
    exit(1);
  }
  return p;
}

char *calloc_fail(int s ,
                  int n )

{
  char *p;
  p = calloc((unsigned)s,(unsigned)n);
  if (p==NULL) {
    fprintf(stderr,"%s: out of memory (requested %d x %d)\n",my_name,s,n);
    exit(1);
  }
  return p;
}

POINT_LIST *new_point(POINT_LIST **spare_points ,
                      real x ,
                      real y ,
                      real z )

{
  POINT_LIST *point;
  if (spare_points == NULL || *spare_points == NULL)
    point = (POINT_LIST*)malloc_fail(sizeof(POINT_LIST));
  else {
    point = *spare_points;
    *spare_points = (*spare_points)->next;
  }
  point->next = NULL;
  point->p.x = x;
  point->p.y = y;
  point->p.z = z;
  return point;
}

char *strip_quotes(char *str )

{
  char *p;
  while (*str==' ' || *str=='\t') str++;
  if (*str=='"' || *str=='\'' || *str=='`') str++;
  p = str;
  while (p[0] && p[1]) p++;
  while ((*p==' ' || *p=='\t' || *p=='\n') && p>str) p--;
  if (*p!='"' && *p!='\'' && *p!='`') p++;
  *p=0;
  return str;
}

POLYGON *list_to_poly(POINT_LIST *points ,
                      char *text ,
                      POINT_LIST **last_point ,
                      int is_poly )

{
  int n;
  POINT_LIST *p,*lp;
  POLYGON *poly;
  real z_max;
  SINGLE_POINT v1, v2;
  float length, sqrt3, inprod, div1, div2;

  *last_point = NULL;
  if (points==NULL) return NULL;
  poly = (POLYGON*)malloc_fail(sizeof(POLYGON));
  if (is_poly)
    {
      poly->type=POLY;
      /* calculate normal vector */
      v1.x = Transform.x_scale * (points->p.x - points->next->p.x);
      v1.y = Transform.y_scale * (points->p.y - points->next->p.y);
      v1.z = Transform.z_scale * (points->p.z - points->next->p.z);

      v2.x = Transform.x_scale * (points->p.x - points->next->next->p.x);
      v2.y = Transform.y_scale * (points->p.y - points->next->next->p.y);
      v2.z = Transform.z_scale * (points->p.z - points->next->next->p.z);

      div1 = (v2.y * v1.z - v1.y * v2.z);
      div2 = (v2.z * v1.y - v1.z * v2.y);

      if(div1 == 0 || div2 == 0)
	{
	  poly->norm.x = 0.0;
	  if(v1.z == 0 && v2.z == 0)
	    {
	      poly->norm.y = 0.0;
	      poly->norm.z = 1.0;
	    }
	  else
	    {
	      poly->norm.y = 1.0;
	      if(v1.z != 0)
		poly->norm.z = - v1.y / v1.z;
	      else
		poly->norm.z = - v2.y / v2.z;
	    }
	}
      else
	{
	  poly->norm.x = 1.0;
	  poly->norm.y = (v1.x * v2.z - v1.z * v2.x) / div1;
	  poly->norm.z = (v1.x * v2.y - v1.y * v2.x) / div2;
	}

      /* normalize it */
      length = (float)sqrt((double)(  poly->norm.x * poly->norm.x
				    + poly->norm.y * poly->norm.y
				    + poly->norm.z * poly->norm.z));
      poly->norm.x /= length;
      poly->norm.y /= length;
      poly->norm.z /= length;

      inprod = fabs((double)(  Style.light_source.x * poly->norm.x
			     + Style.light_source.y * poly->norm.y
			     + Style.light_source.z * poly->norm.z));
	       
      poly->brightness = inprod;
    }
  else
    poly->type=LINE;
  n = 1;
  for (p=points;p->next!=NULL;p = p->next) n++;
  if (n>1 && p->p.x==points->p.x && p->p.y==points->p.y && p->p.z==points->p.z)
    { poly->type=POLY; n--; }

  poly->n_points = n;
  poly->next = NULL;
  poly->text = NULL;
  if (has_content(text)) {
    poly->text = malloc_fail(strlen(text)+1);
    strcpy(poly->text,text);
  }
  poly->points = (POINT*)calloc_fail(sizeof(POINT),n);
  z_max = -MAXFLOAT;
  /*
   * transfer the points
   */
  p=points;
  for (n=0;n<poly->n_points;n++) {
    poly->points[n].world = p->p;
    if (p->p.z > z_max) z_max = p->p.z;
    lp = p;
    p = p->next;
  }
  poly->z_pos = z_max; /* z_sum / poly->n_points; */
  *last_point = lp;
  return(poly);
}

int has_content(char *p )

{
  if (p==NULL) return(0);
  for (;*p!=0;p++) if (!isspace(*p) && isprint(*p)) return(1);
  return(0);
}

/*
 * add_poly()
 * convert the point list to a vector, make a polygon record, and
 * add it to the list of polygons
 */
int add_poly(POLYGON **poly_list ,
             POINT_LIST *point_list ,
             POINT_LIST **spare_points ,
             char *text ,
             int is_poly )

{
  POLYGON *poly;
  POINT_LIST *last_point;
  if (!point_list) return 0;
  poly = list_to_poly(point_list,text,&last_point,is_poly);
  if (last_point!=NULL && spare_points != NULL) {
    last_point->next = *spare_points;
    *spare_points = point_list;
  }
  if (poly!=NULL) {
    poly->next = *poly_list;
    *poly_list = poly;
    return 1;
  } else {
    return 0;
  }
}

char *find_binary_format_text(char *str ,
                              int len ,
                              FILE *in ,
                              int record )

{
  char c;
  char *t;
  if ((c=getc(in))!='t') {
    ungetc(c,in);
    return NULL;
  } else {
    t = str;
    while ((*t=getc(in))!=0 && *t!=EOF && *t!='\n') {
      t++;
      if (t >= str+len) {
	fprintf(stderr,"%s: string too long!, point %d\n",my_name,record);
	exit(1);
      }
    }
    *t = 0;
    return str;
  }
}

int read_polys(STYLE *s ,
               POLYGON **poly_list ,
               POLYGON **label_list ,
               FILE *in )

{
  POINT_LIST *point_list,*point_cur,*spare_points;
  float x,y,z;
  char str[500],*t,label[500];
  int c;
  int n_polys;
  int done,n,record;
  point_list = point_cur = spare_points = 0;
  /*
   * The first line tells us the format of the file, it will
   * be an ascii line.  If it is not recognised, we just read text.
   */
  fgets(str,500,in);
  n_polys = 0;
  record = 0;
  if (!strcmp(str,"binary format float4\n")) {
    /*
     * read a binary format input file
     */
    done = 0;
    point_list = point_cur = NULL;
    while (!done && !feof(in)) {
      c = getc(in);
      while (c==' ') c = getc(in);
      if (c==EOF) break;
      str[0] = 0;
      record++;
      if (c == 'm' || c == 'n') {
	if (fread(&x,sizeof(x),1,in)!=1 || fread(&y,sizeof(y),1,in)!=1 || fread(&z,sizeof(z),1,in)!=1)
	  done = 1;
	DB( else printf("%c %g %g %g\n",c,x,y,z); )
      } DB( else printf("%c\n",c); )
      if (!done) {
	switch (c) {
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	case '.': /* read a regular point in ascii */
	  ungetc(c,in);
	  t = fgets(str,500,in);
	  if (s->dimension == D3)
	    n = sscanf(str," %e %e %e %[^\n]",&x,&y,&z,label);
	  else {
	    n = sscanf(str," %e %e %[^\n]",&x,&y,label);
	    if (n) n++;
	    z = 0.0;
	  }
	  if (n==3) label[0]=0;
	  DBDATA(fprintf(stderr,"Ascii point, %g %g %g %s\n",x,y,z,label););
	  if (n<3) {
	    fprintf(stderr,"%s: couldn't find 3 numbers on input, point %d\n",
		    my_name,record);
	    exit(1);
	  }
	  /* add another point to the list of points */
	  if (point_cur == NULL)
	    point_list = point_cur = new_point(&spare_points,x,y,z);
	  else
	    point_cur = point_cur->next = new_point(&spare_points,x,y,z);
	  if (n==4) {
	    n_polys += add_poly(poly_list,point_list,&spare_points,
				strip_quotes(label),0);
	    point_list = point_cur = NULL;
	  }
	  break;
	  
	case '"': /* read an ascii label */
	  ungetc(c,in);
	  t = fgets(str,500,in);
	  DBDATA(fprintf(stderr,"Ascii label %s\n",t););
	  n_polys += add_poly(poly_list,point_list,&spare_points,
			      strip_quotes(t),0);
	  point_list = point_cur = NULL;
	  break;

	case 'j': /* end a polygon and join it back to the first point */
  	  t = find_binary_format_text(str,500,in,record);
	  DBDATA(fprintf(stderr,"j record %s\n",t););
	  n_polys += add_poly(poly_list,point_list,&spare_points,t,1);
	  point_list = point_cur = NULL;
	  break;

	case 'm': /* begin a new point, line or polygon */
	  DBDATA(fprintf(stderr,"m record, %g %g %g\n",x,y,z););
	  n_polys += add_poly(poly_list,point_list,&spare_points,NULL,0);
	  point_cur = point_list = new_point(&spare_points,x,y,z);
	  if (t = find_binary_format_text(str,500,in,record)) {
	    n_polys += add_poly(poly_list,point_list,&spare_points,t,0);
	    point_list = point_cur = NULL;
	  }
	  break;

	case 'n': /* continue a line */
	  DBDATA(fprintf(stderr,"n record, %g %g %g\n",x,y,z););
	  if (point_cur == NULL)
	    point_list = point_cur = new_point(&spare_points,x,y,z);
	  else
	    point_cur = point_cur->next = new_point(&spare_points,x,y,z);
	  break;

	case 'J': /* end a polygon and join it back to the first point - ascii format */
  	  t = find_binary_format_text(str,500,in,record);
	  DBDATA(fprintf(stderr,"J record %s\n",t););
	  n_polys += add_poly(poly_list,point_list,&spare_points,t,1);
	  point_list = point_cur = NULL;
	  break;

	case 'M': /* begin a new point, line or polygon - ascii format */
	  t = fgets(str,500,in);
	  if (t==NULL || sscanf(t," %e %e %e",&x,&y,&z)!=3) {
	    fprintf(stderr,"%s: bad format on M input, point %d\n",
		    my_name,c,record);
	    exit(1);
	  }
	  DBDATA(fprintf(stderr,"M record, %g %g %g\n",x,y,z););
	  n_polys += add_poly(poly_list,point_list,&spare_points,NULL,0);
	  point_cur = point_list = new_point(&spare_points,x,y,z);
	  if (t = find_binary_format_text(str,500,in,record)) {
	    n_polys += add_poly(poly_list,point_list,&spare_points,t,0);
	    point_list = point_cur = NULL;
	  }
	  break;

	case 'N': /* continue a line - ascii format */
	  t = fgets(str,500,in);
	  if (t==NULL || sscanf(t," %e %e %e",&x,&y,&z)!=3) {
	    fprintf(stderr,"%s: bad format on N input, point %d\n",my_name,c,
		    record);
	    exit(1);
	  }
	  DBDATA(fprintf(stderr,"N record, %g %g %g\n",x,y,z););
	  if (point_cur == NULL)
	    point_list = point_cur = new_point(&spare_points,x,y,z);
	  else
	    point_cur = point_cur->next = new_point(&spare_points,x,y,z);
	  break;

	case 't': /* some text to place at the last point
		     also causes then end of a line */
	case 'L': /* label in view co-ordinates */

	  ungetc('t',in);
	  if (t = find_binary_format_text(str,500,in,record)) {
	    if (sscanf(t,"ABEL: %e %e %[^\n]",&x,&y,label)==3) {
	      DBDATA(fprintf(stderr,"LABEL record, %g %g %s\n",x,y,label););
	      n_polys += add_poly(poly_list,point_list,&spare_points,NULL,0);
	      z = 0.0;
	      point_list = new_point(&spare_points,x,y,z);
	      add_poly(label_list,point_list,&spare_points,
		       strip_quotes(label),0);
	      point_list = point_cur = NULL;
	    } else {
	      DBDATA(fprintf(stderr,"t record %s\n",t););
	      n_polys += add_poly(poly_list,point_list,&spare_points,t,0);
	      point_list = point_cur = NULL;
	    }
	  } else {
	    fprintf(stderr,"%s: internal error: binary text\n",my_name);
	    exit(1);
	  }
	  break;
	default:
	  fprintf(stderr,"%s: bad format on input, char %d, point %d\n",
		  my_name,c,record);
	  exit(1);
	}
      }
    }
    n_polys += add_poly(poly_list,point_list,&spare_points,NULL,0);
  } else {
    /*
     * read an ascii format input file
     */
    do {
      record++;
      n = sscanf(str,"LABEL: %e %e %[^\n]",&x,&y,label);
      if (n>0 && n<3) {
	fprintf(stderr,"%s: bad format on label, point %d\n",my_name,record);
	exit(1);
      } else if (n==3) {
	n_polys += add_poly(poly_list,point_list,&spare_points,NULL,0);
	z = 0.0;
	point_list = new_point(&spare_points,x,y,z);
	add_poly(label_list,point_list,&spare_points,strip_quotes(label),0);
	point_list = point_cur = NULL;
      } else { /* n==0 */
	if (s->dimension == D3)
	  n = sscanf(str," %e %e %e %[^\n]",&x,&y,&z,label);
	else {
	  n = sscanf(str," %e %e %[^\n]",&x,&y,label);
	  if (n) n++;
	  z = 0.0;
	}
	if (n==0) {
	  /*
	   * just a label
	   */
	  n_polys += add_poly(poly_list,point_list,&spare_points,strip_quotes(str),0);
	  point_list = point_cur = NULL;
	} else if (n>=3) { /* add another point to the list of points */
	  if (point_cur == NULL)
	    point_list = point_cur = new_point(&spare_points,x,y,z);
	  else
	    point_cur = point_cur->next = new_point(&spare_points,x,y,z);
	} else if (n>0) {
	  fprintf(stderr,"%s: couldn't find 3 numbers on input, line %d\n",
		  my_name,record);
	  exit(1);
	}
	if (n==4) {
	  n_polys += add_poly(poly_list,point_list,&spare_points,strip_quotes(label),0);
	  point_list = point_cur = NULL;
	}
      }
    } while (fgets(str,500,in)!=NULL);
    n_polys += add_poly(poly_list,point_list,&spare_points,NULL,0);
  }
  return n_polys;
}

int compare_poly(POLYGON **poly1 ,
                 POLYGON **poly2 )

{
  if ((*poly1)->z_pos < (*poly2)->z_pos) return -1;
  if ((*poly1)->z_pos > (*poly2)->z_pos) return 1;
  if ((*poly1)->z_pos == (*poly2)->z_pos) return 0;
}

int show_poly_vec_world(int n_polys ,
                        POLYGON **poly_vec )

{
  int i;
  printf("WORLD POLYGON VECTOR:\n");
  for (i=0;i<n_polys;i++)
    show_poly_world(poly_vec[i]);
  printf("\n");
}

int show_poly_list_world(POLYGON *poly_list )

{
  POLYGON *poly;
  printf("WORLD POLYGON LIST:\n");
  for (poly = poly_list ; poly!=NULL ; poly = poly->next)
    show_poly_world(poly);
  printf("\n");
}

int show_poly_world(POLYGON *poly )

{
  int i;
  if (poly==NULL) {
    printf("!!! NULL pointer to polygon !!!\n");
    return;
  }
  printf("World %s, n=%d z=%10g text=\"%s\" (",(poly->type==LINE?"LINE":"POLY"),poly->n_points,poly->z_pos,poly->text);
  for (i=0;i<poly->n_points;i++) {
    show_single_point(&(poly->points[i].world));
    /* printf("(%g %g %g)",poly->points[i].world.x,poly->points[i].world.y,
       poly->points[i].world.z); */
    if (i<poly->n_points-1) putchar(' ');
  }
  printf(")\n");
}

int show_poly_vec_view(int n_polys ,
                       POLYGON **poly_vec )

{
  int i;
  printf("VIEW POLYGON VECTOR:\n");
  for (i=0;i<n_polys;i++)
    show_poly_view(poly_vec[i]);
  printf("\n");
}

int show_poly_list_view(POLYGON *poly_list )

{
  POLYGON *poly;
  printf("VIEW POLYGON LIST:\n");
  for (poly = poly_list ; poly!=NULL ; poly = poly->next)
    show_poly_view(poly);
  printf("\n");
}

int show_poly_view(POLYGON *poly )

{
  int i;
  if (poly==NULL) {
    printf("!!! NULL pointer to polygon !!!\n");
    return;
  }
  printf("View %s, n=%d z=%6g text=\"%s\" (",(poly->type==LINE?"LINE":"POLY"),poly->n_points,poly->z_pos,poly->text);
  for (i=0;i<poly->n_points;i++) {
    show_single_point(&(poly->points[i].view));
    /* printf("(%g %g %g)",poly->points[i].view.x,poly->points[i].view.y,poly->points[i].view.z); */
    if (i<poly->n_points-1) putchar(' ');
  }
  printf(")\n");
}

int show_single_point(SINGLE_POINT *p )

{
  printf("(%g %g %g)",p->x,p->y,p->z);
}

POLYGON **list_to_vec(POLYGON *poly_list ,
                      int n_polys )

{
  int i;
  POLYGON **vec;
  POLYGON *poly;
  vec = (POLYGON**)calloc_fail(sizeof(POLYGON*),n_polys);
  for ((poly = poly_list),i=0; poly!=NULL; (poly = poly->next),i++) {
    vec[i] = poly;
  }
  return vec;
}

int show_min_max(MIN_MAX *m )

{
  printf("MIN:  ");
  show_single_point(&(m->min));
  printf("\nMAX:  ");
  show_single_point(&(m->max));
  printf("\nEDGE: ");
  show_single_point(&(m->edge));
  printf("\n");
}

int find_min_max_world(POLYGON *poly_list ,
                       MIN_MAX *m )

{
  POLYGON *poly;
  int i;
  m->min.x = m->min.y = m->min.z = 1.0;
  m->max.x = m->max.y = m->max.z = 0.0;
  for (poly = poly_list; poly!=NULL; poly = poly->next)
    for (i=0;i<poly->n_points;i++) {
      set_min_max(poly->points[i].world.x,m->min.x,m->max.x);
      set_min_max(poly->points[i].world.y,m->min.y,m->max.y);
      set_min_max(poly->points[i].world.z,m->min.z,m->max.z);
    }
  m->edge.x = m->max.x - m->min.x;
  m->edge.y = m->max.y - m->min.y;
  m->edge.z = m->max.z - m->min.z;
}

int add_bounds_label(MIN_MAX *m ,
                     POLYGON **label_list )

{
  char s[100];
  sprintf(s,"%g -x- %g  %g -y- %g  %g -z- %g",
	  m->min.x,m->max.x,m->min.y,m->max.y,m->min.z,m->max.z);
  add_poly(label_list,new_point(NULL,1.0,-1.0,0.0),NULL,s,0);
}

int find_min_max_view(POLYGON *poly_list ,
                      MIN_MAX *m )

{
  POLYGON *poly;
  int i;
  m->min.x = m->min.y = m->min.z = 1.0;
  m->max.x = m->max.y = m->max.z = 0.0;
  for (poly = poly_list; poly!=NULL; poly = poly->next)
    for (i=0;i<poly->n_points;i++) {
      set_min_max(poly->points[i].view.x,m->min.x,m->max.x);
      set_min_max(poly->points[i].view.y,m->min.y,m->max.y);
      set_min_max(poly->points[i].view.z,m->min.z,m->max.z);
    }
  m->edge.x = m->max.x - m->min.x;
  m->edge.y = m->max.y - m->min.y;
  m->edge.z = m->max.z - m->min.z;
}

int add_axis(POLYGON **poly_list ,
             MIN_MAX *m ,
             STYLE *s )

{
  POINT_LIST *point_list,*spare_points;
  int n=0;
  point_list = spare_points = NULL;
  switch (s->axis_style) {
  case NONE:
    break;
  case AXIS:
    if (m->max.x > m->min.x) {
      point_list = new_point(&spare_points,m->min.x,m->min.y,m->min.z);
      point_list->next = new_point(&spare_points,m->max.x,m->min.y,m->min.z);
      n++;
      add_poly(poly_list,point_list,&spare_points,getenv("3D_X_AXIS_LABEL"),0);
    }
    if (m->max.y > m->min.y) {
      point_list = new_point(&spare_points,m->min.x,m->min.y,m->min.z);
      point_list->next = new_point(&spare_points,m->min.x,m->max.y,m->min.z);
      n++;
      add_poly(poly_list,point_list,&spare_points,getenv("3D_Y_AXIS_LABEL"),0);
    }
    if (s->dimension==D3 && m->max.z > m->min.z) {
      point_list = new_point(&spare_points,m->min.x,m->min.y,m->min.z);
      point_list->next = new_point(&spare_points,m->min.x,m->min.y,m->max.z);
      n++;
      add_poly(poly_list,point_list,&spare_points,getenv("3D_Z_AXIS_LABEL"),0);
    }
    break;
  case BOX:
    point_list = new_point(&spare_points,m->min.x,m->min.y,m->min.z);
    point_list->next = new_point(&spare_points,m->max.x,m->min.y,m->min.z);
    add_poly(poly_list,point_list,&spare_points,getenv("3D_X_AXIS_LABEL"),0);

    point_list = new_point(&spare_points,m->min.x,m->min.y,m->min.z);
    point_list->next = new_point(&spare_points,m->min.x,m->max.y,m->min.z);
    add_poly(poly_list,point_list,&spare_points,getenv("3D_Y_AXIS_LABEL"),0);

    point_list = new_point(&spare_points,m->max.x,m->min.y,m->min.z);
    point_list->next = new_point(&spare_points,m->max.x,m->max.y,m->min.z);
    add_poly(poly_list,point_list,&spare_points,NULL,0);

    point_list = new_point(&spare_points,m->min.x,m->max.y,m->min.z);
    point_list->next = new_point(&spare_points,m->max.x,m->max.y,m->min.z);
    add_poly(poly_list,point_list,&spare_points,NULL,0);

    n = 4;
    if (s->dimension==D2) break;
    
    point_list = new_point(&spare_points,m->min.x,m->min.y,m->min.z);
    point_list->next = new_point(&spare_points,m->min.x,m->min.y,m->max.z);
    add_poly(poly_list,point_list,&spare_points,getenv("3D_Z_AXIS_LABEL"),0);

    point_list = new_point(&spare_points,m->max.x,m->min.y,m->min.z);
    point_list->next = new_point(&spare_points,m->max.x,m->min.y,m->max.z);
    add_poly(poly_list,point_list,&spare_points,NULL,0);

    point_list = new_point(&spare_points,m->min.x,m->max.y,m->min.z);
    point_list->next = new_point(&spare_points,m->min.x,m->max.y,m->max.z);
    add_poly(poly_list,point_list,&spare_points,NULL,0);

    point_list = new_point(&spare_points,m->max.x,m->max.y,m->min.z);
    point_list->next = new_point(&spare_points,m->max.x,m->max.y,m->max.z);
    add_poly(poly_list,point_list,&spare_points,NULL,0);

    point_list = new_point(&spare_points,m->min.x,m->min.y,m->max.z);
    point_list->next = new_point(&spare_points,m->max.x,m->min.y,m->max.z);
    add_poly(poly_list,point_list,&spare_points,NULL,0);

    point_list = new_point(&spare_points,m->min.x,m->max.y,m->max.z);
    point_list->next = new_point(&spare_points,m->max.x,m->max.y,m->max.z);
    add_poly(poly_list,point_list,&spare_points,NULL,0);

    point_list = new_point(&spare_points,m->min.x,m->min.y,m->max.z);
    point_list->next = new_point(&spare_points,m->min.x,m->max.y,m->max.z);
    add_poly(poly_list,point_list,&spare_points,NULL,0);

    point_list = new_point(&spare_points,m->max.x,m->min.y,m->max.z);
    point_list->next = new_point(&spare_points,m->max.x,m->max.y,m->max.z);
    add_poly(poly_list,point_list,&spare_points,NULL,0);
    
    n=12;
    break;
  default:
    fprintf(stderr,"%s: bad axis style (%d) in add_axis()\n",s->axis_style);
    exit(1);
  }
  return n;
}

int calc_view_scale(TRANSFORM *t ,
                    MIN_MAX *m ,
                    STYLE *s)

{
  POLYGON *poly;
  POINT_LIST *point_list,*point_cur,*spare_points;
  MIN_MAX mv;
  real edge;

  /* calculate sin and cos of all rotations */
  t->sin_x_rot = sin(DEG_TO_RAD(t->x_rot));
  t->sin_y_rot = sin(DEG_TO_RAD(t->y_rot));
  t->sin_z_rot = sin(DEG_TO_RAD(t->z_rot));
  t->cos_x_rot = cos(DEG_TO_RAD(t->x_rot));
  t->cos_y_rot = cos(DEG_TO_RAD(t->y_rot));
  t->cos_z_rot = cos(DEG_TO_RAD(t->z_rot));

  /* initialize view parameters for trial run */
  t->x_view_scale = 1.0;
  t->y_view_scale = 1.0;
  t->z_view_scale = 1.0;
  t->x_view_move = 0.0;
  t->y_view_move = 0.0;
  t->z_view_move = 0.0;

  /*
   * calculate the translation to put the centre of the object at the origin
   */
  t->x_move = -(m->max.x + m->min.x)/2;
  t->y_move = -(m->max.y + m->min.y)/2;
  t->z_move = -(m->max.z + m->min.z)/2;
  edge = sqrt(m->edge.x*m->edge.x + m->edge.y*m->edge.y + m->edge.z*m->edge.z);
  t->norm_proj_focus = t->proj_focus * 3 * edge;
  t->norm_proj_plane = t->proj_plane * edge;
  
  poly = NULL;
  point_list = point_cur = spare_points = NULL;
  point_cur = point_list	= new_point(&spare_points,m->min.x,m->min.y,
					    m->min.z);
  point_cur = point_cur->next	= new_point(&spare_points,m->max.x,m->min.y,
					    m->min.z);
  point_cur = point_cur->next	= new_point(&spare_points,m->max.x,m->max.y,
					    m->min.z);
  point_cur = point_cur->next	= new_point(&spare_points,m->max.x,m->max.y,
					    m->max.z);
  point_cur = point_cur->next	= new_point(&spare_points,m->max.x,m->min.y,
					    m->max.z);
  point_cur = point_cur->next	= new_point(&spare_points,m->min.x,m->min.y,
					    m->max.z);
  point_cur = point_cur->next	= new_point(&spare_points,m->min.x,m->max.y,
					    m->max.z);
  point_cur = point_cur->next	= new_point(&spare_points,m->min.x,m->max.y,
					    m->min.z);
  add_poly(&poly,point_list,&spare_points,NULL,0);
  transform_poly_list(poly,t);
  find_min_max_view(poly,&mv);
  DB( printf("WORLD CORNER POLY:\n"); show_poly_world(poly); )
  DB( printf("VIEW CORNER POLY:\n"); show_poly_view(poly); )
  DB( printf("VIEW MIN MAX:\n"); show_min_max(&mv); )
  t->x_view_scale = t->x_frac/mv.edge.x;
  t->y_view_scale = t->y_frac/mv.edge.y;
  if (s->scale_style==FIRM)
    t->x_view_scale = t->y_view_scale = MIN(t->x_view_scale,t->y_view_scale);
  t->x_view_move = t->x_low;
  t->y_view_move = t->y_low;
  t->x_view_move -= mv.min.x * t->x_view_scale;
  t->y_view_move -= mv.min.y * t->y_view_scale;
  /* t->z_view_move -= mv.min.z; */
  /* delete - we just toss away the memory allocated for the points above */
  /* for debugging: */
  DB( transform_poly_list(poly,t); )
  DB( printf("RETRANSFORMED VIEW CORNER POLY:\n"); show_poly_view(poly); )
}


int transform_poly_list(POLYGON *p ,
                        TRANSFORM *t )

{
  for (;p!=NULL;p = p->next) transform_poly(p,t);
}

int transform_poly(POLYGON *p ,
                   TRANSFORM *t )

{
  real z_sum=0.0;
  int i;
  for (i=0;i<p->n_points;i++) {
    transform_point(p->points+i,t);
    z_sum += p->points[i].view.z;
  }
  p->z_pos = z_sum/p->n_points;
}

int transform_point(POINT *p ,
                    TRANSFORM *t )

{
  register real x1,y1,z1,x2,y2,z2;
  /* translate & scale to bring object centre to origin */
  x1 = t->x_scale*p->world.x + t->x_move;
  y1 = t->y_scale*p->world.y + t->y_move;
  z1 = t->z_scale*p->world.z + t->z_move;
  /* rotate around y axis */
  z2 = z1*t->cos_y_rot + x1*t->sin_y_rot;
  x2 = -z1*t->sin_y_rot + x1*t->cos_y_rot;
  y2 = y1;
  /* rotate around z axis */
  x1 = x2*t->cos_z_rot + y2*t->sin_z_rot;
  y1 = -x2*t->sin_z_rot + y2*t->cos_z_rot;
  z1 = z2;
  /* rotate around x axis */
  y2 = y1*t->cos_x_rot - z1*t->sin_x_rot;
  z2 = y1*t->sin_x_rot + z1*t->cos_x_rot;
  x2 = x1;
  /* scale according to distance from the projection focus */
  if (t->norm_proj_plane != 0.0) {
    p->view.x = (x2 * t->x_view_scale * 
		 t->norm_proj_plane/(t->norm_proj_focus-z2)) + t->x_view_move;
    p->view.y = (y2 * t->y_view_scale * 
		 t->norm_proj_plane/(t->norm_proj_focus-z2)) + t->y_view_move;
  } else {
    p->view.x = (x2 * t->x_view_scale) + t->x_view_move;
    p->view.y = (y2 * t->y_view_scale) + t->y_view_move;
  }
  p->view.z = z2;
}

int output_prologue(TRANSFORM *t ,
                    STYLE *s )

{
  /*
   * prologue
   */
  switch (s->output_style) {
  case GRAPH:
    if (s->border)
      fprintf(out,"%.5f %.5f\n%.5f %.5f\n%.5f %.5f\n%.5f %.5f\n%.5f %.5f \" \n",
	     t->x_low,t->y_low,
	     t->x_low+t->x_frac,t->y_low,
	     t->x_low+t->x_frac,t->y_low+t->y_frac,
	     t->x_low,t->y_low+t->y_frac,
	     t->x_low,t->y_low);
    else if (s->invisible_border)
      fprintf(out,"%.5f %.5f \" \n%.5f %.5f \" \n%.5f %.5f \" \n%.5f %.5f \" \n",
	     t->x_low,t->y_low,
	     t->x_low+t->x_frac,t->y_low,
	     t->x_low+t->x_frac,t->y_low+t->y_frac,
	     t->x_low,t->y_low+t->y_frac);
    break;
  case POST_SCRIPT:
    if (s->border)
      fprintf(out,"save %.0f %.0f moveto %.0f %.0f lineto %.0f %.0f lineto %.0f %.0f lineto closepath stroke restore\n",
	     t->x_low*s->ps_scale,t->y_low*s->ps_scale,
	     (t->x_low+t->x_frac)*s->ps_scale,t->y_low*s->ps_scale,
	     (t->x_low+t->x_frac)*s->ps_scale,(t->y_low+t->y_frac)*s->ps_scale,
	     t->x_low*s->ps_scale,(t->y_low+t->y_frac)*s->ps_scale);
    else if (s->invisible_border)
      fprintf(out,"save %.0f %.0f moveto %.0f %.0f moveto %.0f %.0f moveto %.0f %.0f moveto restore\n",
	     t->x_low*s->ps_scale,t->y_low*s->ps_scale,
	     (t->x_low+t->x_frac)*s->ps_scale,t->y_low*s->ps_scale,
	     (t->x_low+t->x_frac)*s->ps_scale,(t->y_low+t->y_frac)*s->ps_scale,
	     t->x_low*s->ps_scale,(t->y_low+t->y_frac)*s->ps_scale);
    break;
#ifdef HAVE_PLOT
  case PLOT:
    openpl();
    pl_space(s);
    if (s->erase) erase();
    linemod("solid");
    if (s->border) {
      pl_move(s,t->x_low,t->y_low);
      pl_cont(s,t->x_low+t->x_frac,t->y_low);
      pl_cont(s,t->x_low+t->x_frac,t->y_low+t->y_frac);
      pl_cont(s,t->x_low,t->y_low+t->y_frac);
      pl_cont(s,t->x_low,t->y_low);
    } else if (s->invisible_border) {
      pl_move(s,t->x_low,t->y_low);
      pl_move(s,t->x_low+t->x_frac,t->y_low);
      pl_move(s,t->x_low+t->x_frac,t->y_low+t->y_frac);
      pl_move(s,t->x_low,t->y_low+t->y_frac);
    }
    break;
#endif /* HAVE_PLOT */

#ifdef HAVE_X
  case X:
    xopenpl(s);
    break;
#endif /* HAVE_X */

  default:
    bad_style(s);
    exit(1);
  }
}

int output_epilogue(STYLE *s )

{
  /*
   * epilogue
   */
  switch (s->output_style) {
  case GRAPH:
    break;
  case POST_SCRIPT:
    break;
#ifdef HAVE_PLOT
  case PLOT:
    pl_move(s,1.0,1.0); /* to put the cursor in the top right */
    closepl();
    break;
#endif /* HAVE_PLOT */
#ifdef HAVE_X
  case X:
    xclosepl();
    break;
#endif /* HAVE_X */
  default:
    bad_style(s);
    exit(1);
  }
}

int output_poly_vec(int n_polys ,
                    POLYGON **poly_vec ,
                    STYLE *s )

{
  int i;
  /*
   * polygons
   */
  for (i=0;i<n_polys;i++)
    output_poly(poly_vec[i],s);
}

int output_poly_list(POLYGON *poly ,
                     STYLE *s )

{
  for (;poly!=NULL;poly=poly->next)
    output_poly(poly,s);
}

int output_poly(POLYGON *poly ,
                STYLE *s )

{
  int j;
  switch (s->output_style) {
  case GRAPH:
    for (j=0;j<poly->n_points;j++)
      fprintf(out,"%.5f %.5f%c",poly->points[j].view.x,poly->points[j].view.y,
	     (poly->type==POLY || j<poly->n_points-1?  '\n' : ' '));
    if (poly->type==POLY)
      fprintf(out,"%.5f %.5f \"%s\n",poly->points[0].view.x,poly->points[0].view.y,(poly->text==NULL ? " " : poly->text));
    else
      fprintf(out,"\"%s\n",(poly->text==NULL ? " " : poly->text));
    break;
#ifdef HAVE_PLOT
  case PLOT:
    if (poly->n_points>0) {
      pl_move(s,poly->points[0].view.x,poly->points[0].view.y);
      for (j=1;j<poly->n_points;j++)
	pl_cont(s,poly->points[j].view.x,poly->points[j].view.y);
      if (poly->type==POLY)
	pl_cont(s,poly->points[0].view.x,poly->points[0].view.y);
      if (poly->text!=NULL) pl_label(s,poly->text);
    }
    break;
#endif /* HAVE_PLOT */

  case POST_SCRIPT:
    if (poly->type==POLY) {
      fprintf(out, "/polygray %g def\n", poly->brightness);
      if (poly->n_points==3) {
	fprintf(out,"%.0f %.0f %.0f %.0f %.0f %.0f %s\n",
	       poly->points[0].view.x * s->ps_scale,poly->points[0].view.y
		* s->ps_scale,
	       poly->points[1].view.x * s->ps_scale,poly->points[1].view.y
		* s->ps_scale,
	       poly->points[2].view.x * s->ps_scale,poly->points[2].view.y
		* s->ps_scale,
	       (s->remove_hidden ? "trfill" : "tr"));
      } else if (poly->n_points==4) {
	fprintf(out,"%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f %s\n",
	       poly->points[0].view.x * s->ps_scale,poly->points[0].view.y
		* s->ps_scale,
	       poly->points[1].view.x * s->ps_scale,poly->points[1].view.y
		* s->ps_scale,
	       poly->points[2].view.x * s->ps_scale,poly->points[2].view.y
		* s->ps_scale,
	       poly->points[3].view.x * s->ps_scale,poly->points[3].view.y
		* s->ps_scale,
	       (s->remove_hidden ? "sqfill" : "sq"));
      } else {
	fprintf(out,"save newpath ");
	for (j=0;j<poly->n_points;j++)
	  fprintf(out,"%.0f %.0f %s ",
		 poly->points[j].view.x * s->ps_scale,poly->points[j].view.y * s->ps_scale,
		 (j==0 ? "moveto" : "lineto"));
	if (s->remove_hidden)
	  fprintf(out,"closepath gsave polygray setgray fill grestore gsave 0 setgray stroke grestore restore\n");
	else
	  fprintf(out,"closepath gsave 0 setgray stroke grestore restore\n");
      }
    } else { /* LINE */
      fprintf(out,"save newpath ");
      for (j=0;j<poly->n_points;j++)
	fprintf(out,"%.0f %.0f %s ",
	       poly->points[j].view.x * s->ps_scale,poly->points[j].view.y
		* s->ps_scale,
	       (j==0 ? "moveto" : "lineto"));
      fprintf(out,"stroke restore\n");
    }
    if (poly->text!=0) {
      if (print_ps_string(poly->text))
	fprintf(out," %.0f %.0f text\n",
	       poly->points[poly->n_points>0 ? poly->n_points-1 : 0].view.x
		* s->ps_scale,
	       poly->points[poly->n_points>0 ? poly->n_points-1 : 0].view.y
		* s->ps_scale);
    }
    break;

#ifdef HAVE_X
  case X:
    if (poly->type==POLY) {
      xstartpoly();
      for (j=0;j<poly->n_points;j++)
	xcontpoly(poly->points[j].view.x, poly->points[j].view.y);
      xstoppoly(s->remove_hidden, poly->brightness);
    } else { /* line style */
      xstartline();
      for (j=0;j<poly->n_points;j++)
	xcontline(poly->points[j].view.x, poly->points[j].view.y);
      xstopline();
    }
    if (poly->text) {
      j = poly->n_points > 0 ? poly->n_points - 1 : 0;
      xlabel(poly->text, poly->points[j].view.x, poly->points[j].view.y);
    }
    break;
#endif /* HAVE_X */

  default:
    bad_style(s);
    exit(1);
  }
}


int output_label_list(POLYGON *label_list ,
                      TRANSFORM *t ,
                      STYLE *s )

{
  POLYGON *poly;
  real x,y;
  for (poly=label_list;poly!=NULL;poly=poly->next) {
    if (poly->n_points==0) {
      fprintf(stderr,"%s: label poly has no points!\n",my_name);
      exit(1);
    }
    x = s->x_char_size * poly->points[0].world.x;
    y = s->y_char_size * poly->points[0].world.y;
    if (x > 0) x += t->x_low; else x = t->x_low + t->x_frac + x;
    if (y > 0) y += t->y_low; else y = t->y_low + t->y_frac + y;
    switch (s->output_style) {
    case GRAPH:
      fprintf(out,
	      "%.5f %.5f \"%s\n",x,y,(poly->text==NULL ? " " : poly->text));
      break;
    case POST_SCRIPT:
      if (print_ps_string(poly->text))
	fprintf(out, " %.0f %.0f label\n", poly->points[0].world.x,
		poly->points[0].world.y);
      break;

#ifdef HAVE_PLOT
    case PLOT:
      pl_move(s,x,y);
      pl_label(s,poly->text);
      break;
#endif /* HAVE_PLOT */

#ifdef HAVE_X
    case X:
      xlabel(poly->text, x, y);
      break;
#endif /* HAVE_X */

    default:
      bad_style(s);
      exit(1);
    }
  }
}

int print_ps_string(char *str )

{
  if (str!=NULL && has_content(str)) {
    putchar('(');
    for (;*str;str++) {
      if (isprint(*str)) {
	if (*str=='(' || *str==')' || (*str=='\\' && str[1]==0)) putchar('\\');
	putchar(*str);
      }
    }
    putchar(')');
    return 1;
  }
  return 0;
}

/*
 * output_orient - a little-bitty labelled axis in the bottom-right-hand corner
 */
int output_orient(TRANSFORM *t ,
                  STYLE *s )

{
  POLYGON *poly_list=NULL;
  POINT_LIST *point_list,*point_cur;
  POINT_LIST *spare_points=NULL;
  MIN_MAX m;
  TRANSFORM newt;
  point_cur = point_list      = new_point(&spare_points,-0.05,-0.05,-0.05);
  point_cur = point_cur->next = new_point(&spare_points,0.05,-0.05,-0.05);
  add_poly(&poly_list,point_list,&spare_points,"X",0);
  point_cur = point_list      = new_point(&spare_points,0.04,-0.04,-0.05);
  point_cur = point_cur->next = new_point(&spare_points,0.05,-0.05,-0.05);
  point_cur = point_cur->next = new_point(&spare_points,0.04,-0.06,-0.05);
  add_poly(&poly_list,point_list,&spare_points,NULL,0);
  point_cur = point_list      = new_point(&spare_points,-0.05,-0.05,-0.05);
  point_cur = point_cur->next = new_point(&spare_points,-0.05,-0.05,0.05);
  add_poly(&poly_list,point_list,&spare_points,"Z",0);
  point_cur = point_list      = new_point(&spare_points,-0.04,-0.05,0.04);
  point_cur = point_cur->next = new_point(&spare_points,-0.05,-0.05,0.05);
  point_cur = point_cur->next = new_point(&spare_points,-0.06,-0.05,0.04);
  add_poly(&poly_list,point_list,&spare_points,NULL,0);
  point_cur = point_list      = new_point(&spare_points,-0.05,-0.05,-0.05);
  point_cur = point_cur->next = new_point(&spare_points,-0.05,0.05,-0.05);
  add_poly(&poly_list,point_list,&spare_points,"Y",0);
  point_cur = point_list      = new_point(&spare_points,-0.04,0.04,-0.05);
  point_cur = point_cur->next = new_point(&spare_points,-0.05,0.05,-0.05);
  point_cur = point_cur->next = new_point(&spare_points,-0.06,0.04,-0.05);
  add_poly(&poly_list,point_list,&spare_points,NULL,0);

  /*
   * set up a transform record for doing the axis
   */
  newt.norm_proj_focus	= 0; /* no perspective for this */
  newt.norm_proj_plane	= 0;
  newt.x_low		= t->x_low;
  newt.x_frac		= t->x_frac;
  newt.x_view_scale	= 1;
  newt.x_view_move	= 0;
  newt.x_scale		= t->x_scale/fabs(t->x_scale);
  newt.x_move		= 0;
  newt.x_rot		= t->x_rot;
  newt.sin_x_rot	= t->sin_x_rot;
  newt.cos_x_rot	= t->cos_x_rot;
  newt.y_low		= t->y_low;
  newt.y_frac		= t->y_frac;
  newt.y_view_scale	= 1;
  newt.y_view_move	= 0;
  newt.y_scale		= t->y_scale/fabs(t->y_scale);
  newt.y_move		= 0;
  newt.y_rot		= t->y_rot;
  newt.sin_y_rot	= t->sin_y_rot;
  newt.cos_y_rot	= t->cos_y_rot;
  newt.z_scale		= t->z_scale/fabs(t->z_scale);
  newt.z_move		= 0;
  newt.z_rot		= t->z_rot;
  newt.z_view_scale	= 1;
  newt.z_view_move	= 0;
  newt.sin_z_rot	= t->sin_z_rot;
  newt.cos_z_rot	= t->cos_z_rot;

  transform_poly_list(poly_list,&newt);
  find_min_max_view(poly_list,&m);
  newt.x_view_move = t->x_low+t->x_frac - m.max.x -.02;
  newt.y_view_move = t->y_low - m.min.y +.02;
  transform_poly_list(poly_list,&newt);
  output_poly_list(poly_list,s);
}

int output_back_axis(POLYGON *poly_list ,
                     STYLE *s )

{
  MIN_MAX m;
  POLYGON *poly;
  int front_is_corner; /* the front z point is a corner rather than the end of an axis */
  find_min_max_view(poly_list,&m);
  front_is_corner = 0;
  for (poly=poly_list;poly!=NULL;poly=poly->next)
    if (poly->n_points>1
	&& (poly->points[0].view.z==m.max.z
	    || poly->points[1].view.z==m.max.z))
      front_is_corner++;
  front_is_corner = (front_is_corner > 1);
  for (poly=poly_list;poly!=NULL;poly=poly->next)
    if (!front_is_corner
	|| poly->n_points<2
	|| (poly->points[0].view.z!=m.max.z && poly->points[1].view.z!=m.max.z))
    output_poly(poly,s);
}


int output_front_axis(POLYGON *poly_list ,
                      STYLE *s )

{
  MIN_MAX m;
  POLYGON *poly;
  int front_is_corner; /* the front z point is a corner rather than the end of an axis */
  find_min_max_view(poly_list,&m);
  front_is_corner = 0;
  for (poly=poly_list;poly!=NULL;poly=poly->next)
    if (poly->n_points>1
	&& (poly->points[0].view.z==m.max.z || poly->points[1].view.z==m.max.z)) front_is_corner++;
  front_is_corner = (front_is_corner > 1);
  for (poly=poly_list;poly!=NULL;poly=poly->next)
    if (poly->n_points<2
	|| (front_is_corner
	    && (poly->points[0].view.z==m.max.z || poly->points[1].view.z==m.max.z)))
    output_poly(poly,s);
}


#ifdef HAVE_PLOT

pl_space(s)
STYLE *s;
{
  int x0,y0,x1,y1;
  x0 = y0 = 0;
  x1 = y1 = s->plot_scale;
  space(x0,y0,x1,y1);
}

pl_move(s,x,y)
STYLE *s;
real x,y;
{
  int x0,y0;
  x0 = x * s->plot_scale;
  y0 = y * s->plot_scale;
  move(x0,y0);
}

pl_cont(s,x,y)
STYLE *s;
real x,y;
{
  int x0,y0;
  x0 = x * s->plot_scale;
  y0 = y * s->plot_scale;
  cont(x0,y0);
}

pl_point(s,x,y)
STYLE *s;
real x,y;
{
  int x0,y0;
  x0 = x * s->plot_scale;
  y0 = y * s->plot_scale;
  point(x0,y0);
}

pl_label(s,t)
STYLE *s;
char *t;
{
  if (t!=NULL && *t) label(t);
}
#endif /* HAVE_PLOT */


/*
 * return 1 if prefix is an unambigous prefix of the option 'word'
 */

int prefix(char *prefix ,
           char *word )

{
  int matches,len;
  char **option;
  len = strlen(prefix);
  if (strncmp(prefix,word,len)) return 0;
  if (strlen(word) == len) return 1;
  /* we've got a prefix match, check for ambiguity */
  matches = 0;
  option = options_description;
  while (*option) {
    if (option[0][0]=='-' && !strncmp(prefix,option[0]+1,len))
      matches++;
    option++;
  }
  if (matches==1) return 1;
  else return 0;
}

int print_options(void)

{
  char **p = options_description;
  while (*p) printf("%s\n",*p++);
}

int error(void *s,
          void *p)

{
  char str[200];
  sprintf(str,"%s: %s\n",my_name,s);
  fprintf(stderr,str,p);
  exit(1);
}

int bad_style(STYLE *s )   
{
  char *style;

  switch(s->output_style) {
  case POST_SCRIPT:
    style = "PostScript";
    break;
  case PLOT:
    style = "Plot";
    break;
  case GRAPH:
    style = "Graph";
    break;
  case X:
    style = "X";
    break;
  }
  fprintf(stderr, "%s: output in %s format not supported in this version\n",
	  my_name, style);
  exit(1);
}
