#ifndef lint
static char *RCSid = "$Id: graph3d.c,v 1.85 1995/12/02 22:04:32 drd Exp $";
#endif


/* GNUPLOT - graph3d.c */
/*
 * Copyright (C) 1986 - 1993   Thomas Williams, Colin Kelley
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted, 
 * provided that the above copyright notice appear in all copies and 
 * that both that copyright notice and this permission notice appear 
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the modified code.  Modifications are to be distributed 
 * as patches to released version.
 *
 * This software is provided "as is" without express or implied warranty.
 *
 *
 * AUTHORS
 *
 *   Original Software:
 *       Gershon Elber and many others.
 *
 * 19 September 1992  Lawrence Crowl  (crowl@cs.orst.edu)
 * Added user-specified bases for log scaling.
 *
 * 3.6 - split graph3d.c into graph3d.c (graph),
 *                            util3d.c (intersections, etc)
 *                            hidden3d.c (hidden-line removal code)
 *
 * There is a mailing list for gnuplot users. Note, however, that the
 * newsgroup 
 *	comp.graphics.gnuplot 
 * is identical to the mailing list (they
 * both carry the same set of messages). We prefer that you read the
 * messages through that newsgroup, to subscribing to the mailing list.
 * (If you can read that newsgroup, and are already on the mailing list,
 * please send a message info-gnuplot-request@dartmouth.edu, asking to be
 * removed from the mailing list.)
 *
 * The address for mailing to list members is
 *	   info-gnuplot@dartmouth.edu
 * and for mailing administrative requests is 
 *	   info-gnuplot-request@dartmouth.edu
 * The mailing list for bug reports is 
 *	   bug-gnuplot@dartmouth.edu
 * The list of those interested in beta-test versions is
 *	   info-gnuplot-beta@dartmouth.edu
 */

#include <math.h>
#if !defined(sequent) && !defined(apollo) && !defined(alliant)
#include <limits.h>
#endif
#include "plot.h"
#include "setshow.h"


#define KEYWTH ( (int) (4*(t->h_char) + pointsize*(t->h_tic)))

extern TBOOLEAN term_graphics, term_suspended;

static int p_height;
static int p_width;  /* pointsize * t->h_tic */
static int key_entry_height;  /* bigger of t->v_size, pointsize*t->v_tick */

int suppressMove = 0;  /* for preventing moveto while drawing contours */

/*
 * hidden_line_type_above, hidden_line_type_below - controls type of lines
 *   for above and below parts of the surface.
 * hidden_no_update - if TRUE lines will be hidden line removed but they
 *   are not assumed to be part of the surface (i.e. grid) and therefore
 *   do not influence the hidings.
 * hidden_active - TRUE if hidden lines are to be removed.
 */
int hidden_active = FALSE;

/* LITE defines a restricted memory version for MS-DOS, which doesn't
 * use the routines in hidden3d.c
 */

#ifndef LITE
int hidden_line_type_above, hidden_line_type_below;
int hidden_no_update;
#endif /* LITE */


static double LogScale __P((double coord, TBOOLEAN is_log, double log_base_log,
                            char *what,	char *axis));
static void plot3d_impulses __P((struct surface_points *plot));
static void plot3d_lines __P((struct surface_points *plot));
static void plot3d_points __P((struct surface_points *plot));
static void plot3d_dots __P((struct surface_points *plot));
static void cntr3d_impulses __P((struct gnuplot_contours *cntr, struct surface_points *plot));
static void cntr3d_lines __P((struct gnuplot_contours *cntr));
static void cntr3d_points __P((struct gnuplot_contours *cntr, struct surface_points *plot));
static void cntr3d_dots __P((struct gnuplot_contours *cntr));
static void check_corner_height __P((struct coordinate GPHUGE *point, double height[2][2], double depth[2][2]));
static void draw_bottom_grid __P((struct surface_points *plot, int plot_count));
static void xtick_callback __P((int axis, double place, char *text, int grid));
static void ytick_callback __P((int axis, double place, char *text, int grid));
static void ztick_callback __P((int axis, double place, char *text, int grid));
static void setlinestyle __P((int style));

static void boundary3d __P((int scaling, struct surface_points *plots, int count));
#if 0 /* not used */
static double dbl_raise __P((double x, int y));
#endif
static void map_position __P((struct position *pos, unsigned int *x, unsigned int *y, char *what));

/* put entries in the key */
static void key_sample_line __P((int xl, int yl));
static void key_sample_point __P((int xl, int yl, int pointtype));
static void key_text __P((int xl, int yl, char *text));


#if defined(sun386) || defined(AMIGA_SC_6_1)
static double CheckLog __P((TBOOLEAN is_log, double base_log, double x));
#endif


#ifndef max		/* Lattice C has max() in math.h, but shouldn't! */
#define max(a,b) ((a > b) ? a : b)
#endif

#ifndef min
#define min(a,b) ((a < b) ? a : b)
#endif

/*
 * The Amiga SAS/C 6.2 compiler moans about macro envocations causing
 * multiple calls to functions. I converted these macros to inline
 * functions coping with the problem without loosing speed.
 * (MGR, 1993)
 */
#ifdef AMIGA_SC_6_1
__inline static TBOOLEAN i_inrange(int z,int min,int max)
{
  return((min<max) ? ((z>=min)&&(z<=max)) : ((z>=max)&&(z<=min)));
}

__inline static double f_max(double a,double b)
{
  return(max(a,b));
}

__inline static double f_min(double a,double b)
{
  return(min(a,b));
}

#else
#define f_max(a,b) max((a),(b))
#define f_min(a,b) min((a),(b))
#define i_inrange(z,a,b) inrange((z),(a),(b))
#endif

#define inrange(z,min,max) ((min<max) ? ((z>=min)&&(z<=max)) : ((z>=max)&&(z<=min)) )

#define apx_eq(x,y) (fabs(x-y) < 0.001)
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#define SQR(x) ((x) * (x))

/* Define the boundary of the plot
 * These are computed at each call to do_plot, and are constant over
 * the period of one do_plot. They actually only change when the term
 * type changes and when the 'set size' factors change. 
 */

/* in order to allow graphic.c to use clip_draw_line, we must
 * share xleft, xright, ybot, ytop with graphics.c
 */
extern int xleft, xright, ybot, ytop;

int xmiddle, ymiddle, xscaler, yscaler;
static int ptitl_cnt;
static int max_ptitl_len;
static int titlelin;
static int key_rows, key_cols, key_col_wth, yl_ref;
static int ktitle_lines = 0;


/* Boundary and scale factors, in user coordinates */
/* x_min3d, x_max3d, y_min3d, y_max3d, z_min3d, z_max3d are local to this
 * file and are not the same as variables of the same names in other files
 */
/*static double x_min3d, x_max3d, y_min3d, y_max3d, z_min3d, z_max3d;*/
/* sizes are now set in min_array[], max_array[] from command.c */
extern double min_array[], max_array[];
extern int auto_array[], log_array[];
extern double base_array[], log_base_array[];

/* for convenience while converting to use these arrays */
#define x_min3d min_array[FIRST_X_AXIS]
#define x_max3d max_array[FIRST_X_AXIS]
#define y_min3d min_array[FIRST_Y_AXIS]
#define y_max3d max_array[FIRST_Y_AXIS]
#define z_min3d min_array[FIRST_Z_AXIS]
#define z_max3d max_array[FIRST_Z_AXIS]

/* There are several z's to take into account - I hope I get these
 * right !
 *
 * ceiling_z is the highest z in use
 * floor_z   is the lowest z in use
 * base_z is the z of the base
 * min3d_z is the lowest z of the graph area
 * max3d_z is the highest z of the graph area
 *
 * ceiling_z is either max3d_z or base_z, and similarly for floor_z
 * There should be no part of graph drawn outside
 * min3d_z:max3d_z  - apart from arrows, perhaps
 */
 
double ceiling_z, floor_z, base_z;

/* and some bodges while making the change */
#define min3d_z min_array[FIRST_Z_AXIS]
#define max3d_z max_array[FIRST_Z_AXIS]


double xscale3d, yscale3d, zscale3d;


typedef double transform_matrix[4][4];
transform_matrix trans_mat;

static double xaxis_y, yaxis_x, zaxis_x, zaxis_y;

/* the co-ordinates of the back corner */
static double back_x, back_y;

/* the penalty for convenience of using tic_gen to make callbacks
 * to tick routines is that we cant pass parameters very easily.
 * We communicate with the tick_callbacks using static variables
 */

/* unit vector (terminal coords) */
static double tic_unitx, tic_unity;

/* (DFK) Watch for cancellation error near zero on axes labels */
#define SIGNIF (0.01)		/* less than one hundredth of a tic mark */
#define CheckZero(x,tic) (fabs(x) < ((tic) * SIGNIF) ? 0.0 : (x))
#define NearlyEqual(x,y,tic) (fabs((x)-(y)) < ((tic) * SIGNIF))

/* And the functions to map from user to terminal coordinates */
#define map_x(x) (int)(x+0.5) /* maps floating point x to screen */ 
#define map_y(y) (int)(y+0.5)	/* same for y */

/* And the functions to map from user 3D space into normalized -1..1 */
#define map_x3d(x) ((x-x_min3d)*xscale3d-1.0)
#define map_y3d(y) ((y-y_min3d)*yscale3d-1.0)
#define map_z3d(z) ((z-floor_z)*zscale3d-1.0)



/* Initialize the line style using the current device and set hidden styles  */
/* to it as well if hidden line removal is enabled.			     */
static void setlinestyle(style)
int style;
{
    register struct termentry *t = term;

    (*t->linetype)(style);

#ifndef LITE
    if (hidden3d) {
	hidden_line_type_above = style;
	hidden_line_type_below = style;
    }
#endif  /* LITE */
}

#ifndef LITE
#endif /* not LITE */



/* (DFK) For some reason, the Sun386i compiler screws up with the CheckLog 
 * macro, so I write it as a function on that machine.
 *
 * Amiga SAS/C 6.2 thinks it will do too much work calling functions in
 * macro arguments twice, thus I inline theese functions. (MGR, 1993)
 */
#if defined(sun386) || defined(AMIGA_SC_6_1)
#ifdef AMIGA_SC_6_1
__inline
#endif
static double
CheckLog(is_log, base_log, x)
     TBOOLEAN is_log;
     double base_log;
     double x;
{
  if (is_log)
    return(pow(base_log, x));
  else
    return(x);
}
#else
/* (DFK) Use 10^x if logscale is in effect, else x */
#define CheckLog(is_log, base_log, x) ((is_log) ? pow(base_log, (x)) : (x))
#endif /* sun386 || SAS/C */

static double
LogScale(coord, is_log, log_base_log, what, axis)
	double coord;			/* the value */
	TBOOLEAN is_log;			/* is this axis in logscale? */
	double log_base_log;		/* if so, the log of its base */
	char *what;			/* what is the coord for? */
	char *axis;			/* which axis is this for ("x" or "y")? */
{
    if (is_log) {
	   if (coord <= 0.0) {
		  char errbuf[100];		/* place to write error message */
		(void) sprintf(errbuf,"%s has %s coord of %g; must be above 0 for log scale!",
				what, axis, coord);
		  graph_error(errbuf);
	   } else
		return(log(coord)/log_base_log);
    }
    return(coord);
}

/* And the functions to map from user 3D space to terminal coordinates */
void map3d_xy(x, y, z, xt, yt)
double x, y, z;
unsigned int *xt, *yt;
{
    int i,j;
    double v[4], res[4],		     /* Homogeneous coords. vectors. */
	w = trans_mat[3][3];

    v[0] = map_x3d(x); /* Normalize object space to -1..1 */
    v[1] = map_y3d(y);
    v[2] = map_z3d(z);
    v[3] = 1.0;

    for (i = 0; i < 2; i++) {	             /* Dont use the third axes (z). */
        res[i] = trans_mat[3][i];     /* Initiate it with the weight factor. */
        for (j = 0; j < 3; j++) res[i] += v[j] * trans_mat[j][i];
    }

    for (i = 0; i < 3; i++) w += v[i] * trans_mat[i][3];
    if (w == 0) w = 1e-5;

    *xt = (unsigned int) ((res[0] * xscaler / w) + xmiddle);
    *yt = (unsigned int) ((res[1] * yscaler / w) + ymiddle);
}



/* And the functions to map from user 3D space to terminal z coordinate */
int map3d_z(x, y, z)
double x, y, z;
{
    int i, zt;
    double v[4], res,			     /* Homogeneous coords. vectors. */
	w = trans_mat[3][3];

    v[0] = map_x3d(x); /* Normalize object space to -1..1 */
    v[1] = map_y3d(y);
    v[2] = map_z3d(z);
    v[3] = 1.0;

    res = trans_mat[3][2];     	      /* Initiate it with the weight factor. */
    for (i = 0; i < 3; i++) res += v[i] * trans_mat[i][2];
    if(w==0) w= 1e-5;
    for (i = 0; i < 3; i++) w += v[i] * trans_mat[i][3];
    zt = ((int) (res * 16384 / w));
    return  zt;
}

/* borders of plotting area */
/* computed once on every call to do_plot */
static void boundary3d(scaling,plots,count)
	TBOOLEAN scaling;	/* TRUE if terminal is doing the scaling */
	struct surface_points *plots;
	int count;
{
    register struct termentry *t = term;
    int ytlen, i;

   titlelin = 0;

   p_height= pointsize * t->v_tic;
   p_width = pointsize * t->h_tic;
   key_entry_height = pointsize * (t->v_tic) * 1.25;
   if (key_entry_height < (t->v_char) )
	key_entry_height = (t->v_char);

   /* count max_len key and number keys (plot-titles and contour labels) with len > 0 */
   max_ptitl_len = find_maxl_keys3d(plots,count,&ptitl_cnt);
   if ( (ytlen = label_width(key_title,&ktitle_lines)) > max_ptitl_len )
	   max_ptitl_len = ytlen;
   key_col_wth = (max_ptitl_len + 4 ) * (t->h_char) + KEYWTH;

    /* luecken@udel.edu modifications
       sizes the plot to take up more of available resolution */
   if ( lmargin >= 0 ) 
	   xleft = (t->h_char)*lmargin;
   else
	   xleft = (t->h_char)*2 + (t->h_tic);
   xright = (scaling ? 1 : xsize) * (t->xmax) - (t->h_char)*2 - (t->h_tic);
   key_rows = ptitl_cnt;
   key_cols = 1;
   if (key == -1 && key_vpos == TUNDER ) {
	/* calculate max no cols, limited by label-length */
	key_cols = (int)(xright - xleft)/((max_ptitl_len + 4 ) * (t->h_char) + KEYWTH);
	key_rows = (int)(ptitl_cnt/key_cols)+((ptitl_cnt%key_cols)>0);
	/* now calculate actual no cols depending on no rows */
	key_cols = (int)(ptitl_cnt/key_rows)+((ptitl_cnt%key_rows)>0); 
	key_col_wth = (int)(xright - xleft)/key_cols;
        /* key_rows += ktitle_lines; - messes up key - div */
   }
   /* this should also consider the view and number of lines in xformat || yformat || xlabel || ylabel */
   ybot = (t->v_char)*2.5 + 1;  /* an absolute 1, with no terminal-dependent scaling ? */
   if (key_rows&&key_vpos==TUNDER)
      ybot += key_rows*key_entry_height + ktitle_lines*t->v_char;

   if (strlen(title.text)) {
	titlelin++;
 	for(i=0;i<strlen(title.text);i++) {
 		if (title.text[i] == '\\') titlelin++;
 	}
   }
   ytop = (scaling ? 1 : ysize) * (t->ymax) - (t->v_char)*(titlelin+1.5) - 1;
   if (key == -1 && key_vpos != TUNDER ) {
	/* calculate max no rows, limited be ytop-ybot */
	i = (int)(ytop-ybot)/(t->v_char) - 1 - ktitle_lines;
	if (ptitl_cnt > i) {
	    key_cols = (int)(ptitl_cnt/i)+((ptitl_cnt%i)>0);
	    /* now calculate actual no rows depending on no cols */
	    key_rows = (int)(ptitl_cnt/key_cols)+((ptitl_cnt%key_cols)>0);
	}
	key_rows += ktitle_lines;
   }
   if ( key_hpos == TOUT ) {
	xright -= key_col_wth*(key_cols-1) + key_col_wth - 2*(t->h_char); 
   }

   xleft  += t->xmax * xoffset;
   xright += t->xmax * xoffset;
   ytop   += t->ymax * yoffset;
   ybot   += t->ymax * yoffset;
   xmiddle = (xright + xleft) / 2;
   ymiddle = (ytop + ybot) / 2;
   xscaler = (xright - xleft) * 4 / 7;
   yscaler = (ytop - ybot) * 4 / 7;
}

#if 0
/* not used ; anyway, should be done using bitshifts and squares,
 * rather than iteratively
 */
static double dbl_raise(x,y)
double x;
int y;
{
register int i=ABS(y);
double val=1.0;
	while (--i >= 0)
		val *= x;
	if (y < 0 ) return (1.0/val);
	return(val);
}
#endif

/* we precalculate features of the key, to save lots of nested
 * ifs in code - x,y = user supplied or computed position of key
 * taken to be inner edge of a line sample
 */
static int key_sample_left;   /* offset from x for left of line sample */
static int key_sample_right;  /* offset from x for right of line sample */
static int key_point_offset;  /* offset from x for point sample */
static int key_text_left;     /* offset from x for left-justified text */
static int key_text_right;    /* offset from x for right-justified text */
static int key_size_left;     /* distance from x to left edge of box */
static int key_size_right;    /* distance from x to right edge of box */



void do_3dplot(plots, pcount)
struct surface_points *plots;
int pcount;			/* count of plots in linked list */
{
struct termentry *t = term;
int surface;
struct surface_points *this_plot = NULL;
unsigned int xl, yl;
int linetypeOffset = 0;
double ztemp, temp;
struct text_label *this_label;
struct arrow_def *this_arrow;
TBOOLEAN scaling;
transform_matrix mat;
int key_count;
char ss[MAX_LINE_LEN+1], *s, *e;

/* Initiate transformation matrix using the global view variables. */
    mat_rot_z(surface_rot_z, trans_mat);
    mat_rot_x(surface_rot_x, mat);
    mat_mult(trans_mat, trans_mat, mat);
    mat_scale(surface_scale / 2.0, surface_scale / 2.0, surface_scale / 2.0, mat);
    mat_mult(trans_mat, trans_mat, mat);

/* modify min_z/max_z so it will zscale properly. */
    ztemp = (z_max3d - z_min3d) / (2.0 * surface_zscale);
    temp = (z_max3d + z_min3d) / 2.0;
    z_min3d = temp - ztemp;
    z_max3d = temp + ztemp;

#if 0 /* already done in eval_3dplots */
    /* auto-extend ranges to tic-mark for autoscaled axes */
    if (xtics) setup_tics(FIRST_X_AXIS, &xticdef, xformat);
    if (ytics) setup_tics(FIRST_Y_AXIS, &yticdef, yformat);
    if (ztics) setup_tics(FIRST_Z_AXIS, &zticdef, zformat);
#endif

	/* The extrema need to be set even when a surface is not being
	 * drawn.   Without this, gnuplot used to assume that the X and
	 * Y axis started at zero.   -RKC
	 */

    if (polar)
	graph_error("Cannot splot in polar coordinate system.");

/* done in plot3d.c
 *    if (z_min3d == VERYLARGE || z_max3d == -VERYLARGE ||
 *	x_min3d == VERYLARGE || x_max3d == -VERYLARGE ||
 *	y_min3d == VERYLARGE || y_max3d == -VERYLARGE)
 *      graph_error("all points undefined!");
 */
 
    /* If we are to draw the bottom grid make sure zmin is updated properly. */
    if (xtics || ytics || grid) {
	base_z = z_min3d - (z_max3d - z_min3d) * ticslevel;
	if (ticslevel >= 0)
		floor_z = base_z;
	else
		floor_z = z_min3d;

	if (ticslevel < -1)
		ceiling_z = base_z;
	else
		ceiling_z = z_max3d;
    } else {
	floor_z = base_z = z_min3d;
	ceiling_z = z_max3d;
    }

/*  This used be x_max3d == x_min3d, but that caused an infinite loop once. */
    if (fabs(x_max3d - x_min3d) < zero)
	graph_error("x_min3d should not equal x_max3d!");
    if (fabs(y_max3d - y_min3d) < zero)
	graph_error("y_min3d should not equal y_max3d!");
    if (fabs(z_max3d - z_min3d) < zero)
	graph_error("z_min3d should not equal z_max3d!");

#ifndef LITE
    if (hidden3d) {
	struct surface_points *plot;
	int p=0;
        /* Verify data is hidden line removable - grid based. */
      	for (plot = plots; ++p <= pcount; plot = plot->next_sp) {
 	    if (plot->plot_type == DATA3D && !plot->has_grid_topology){
	      fprintf(stderr,"Notice: Cannot remove hidden lines from non grid data\n");
              return;
            }
	
        }
    }
#endif /* not LITE */

/* INITIALIZE TERMINAL */
    if (!term_init) {
	(*t->init)();
	term_init = TRUE;
    }
    screen_ok = FALSE;
    scaling = (*t->scale)(xsize, ysize);

    if (multiplot && term_graphics) {
		if (term_suspended && term->resume)
			(*t->resume)();
		term_suspended = FALSE;
	 } else {
		(*t->graphics)();
		term_graphics = TRUE;
	 }

    /* now compute boundary for plot (xleft, xright, ytop, ybot) */
    boundary3d(scaling,plots,pcount);

/* SCALE FACTORS */
	zscale3d = 2.0/(ceiling_z - floor_z);
	yscale3d = 2.0/(y_max3d - y_min3d);
	xscale3d = 2.0/(x_max3d - x_min3d);

	(*t->linetype)(-2); /* border linetype */

/* PLACE TITLE */
	if (*title.text != 0) {
		strcpy(ss, title.text);
		write_multiline( (unsigned int)((xleft+xright)/2+title.xoffset*t->h_char),
		                 (unsigned int)(ytop+(titlelin+title.yoffset)*(t->h_char)),
		                 ss, CENTRE, JUST_TOP, 0, title.font);
	}

/* PLACE TIMEDATE */
	if (*timelabel.text) {
		char str[MAX_LINE_LEN+1];
		time_t now;
		unsigned int x = t->v_char + timelabel.xoffset * t->h_char;
		unsigned int y = yoffset*ymax + (timelabel.yoffset+1) * t->v_char;

		time(&now);
		strftime(str, MAX_LINE_LEN, timelabel.text, localtime(&now));

		if ((*t->text_angle)(1)) {
			write_multiline(x,y,str,CENTRE, JUST_TOP, 1, timelabel.font);
			(*t->text_angle)(0);
		} else {
			write_multiline(x,y,str,LEFT, JUST_BOT, 0, timelabel.font);
		}
	}

/* PLACE LABELS */
    for (this_label = first_label; this_label!=NULL;
			this_label=this_label->next ) {
	unsigned int x,y;

	map_position(&this_label->place, &x, &y, "label");
	strcpy(ss, this_label->text);
	write_multiline( x,y, this_label->text, this_label->pos, CENTRE, 0, this_label->font);
    }

/* PLACE ARROWS */
    for (this_arrow = first_arrow; this_arrow!=NULL;
	    this_arrow = this_arrow->next ) {
	unsigned int sx,sy,ex,ey;
	map_position(&this_arrow->start, &sx, &sy, "arrow");
	map_position(&this_arrow->end, &ex, &ey, "arrow");
    (*t->linetype)(this_arrow->line);
	(*t->arrow)(sx, sy, ex, ey, this_arrow->head);
    }

#ifndef LITE
    if (hidden3d && draw_surface) {
	init_hidden_line_removal();
	reset_hidden_line_removal();
	hidden_active = TRUE;
    }
#endif /* not LITE */

/* WORK OUT KEY SETTINGS AND DO KEY TITLE / BOX */

	if (key_reverse) {
		key_sample_left= -KEYWTH;
		key_sample_right= 0;
		key_text_left=t->h_char;
		key_text_right=(t->h_char)*(max_ptitl_len+1);
		key_size_right=(t->h_char)*(max_ptitl_len+2);
		key_size_left=(t->h_char) + KEYWTH;
	} else {
		key_sample_left= 0;
		key_sample_right= KEYWTH;
		key_text_left=-(t->h_char)*(max_ptitl_len+1);
		key_text_right=-(t->h_char);
		key_size_left=(t->h_char)*(max_ptitl_len+2);
		key_size_right=(t->h_char)+KEYWTH;
	}
	key_point_offset = (key_sample_left + key_sample_right)/2;

	if (key == -1) {
 	    if ( key_vpos == TUNDER ) {
#if 0
		yl = yoffset * t->ymax + (key_rows) * key_entry_height + (ktitle_lines+2)*t->v_char;
		xl = max_ptitl_len * 1000/( KEYWTH/(t->h_char) + max_ptitl_len + 2);
		xl *= (xright - xleft)/key_cols;
		xl /= 1000;
		xl += xleft;
#else
	    /* maximise no cols, limited by label-length */
	    key_cols = (int)(xright - xleft)/key_col_wth;
	    key_rows = (int)(ptitl_cnt+key_cols-1)/key_cols;
	    /* now calculate actual no cols depending on no rows */
	    key_cols = (int)(ptitl_cnt+key_rows-1)/key_rows; 
	    key_col_wth = (int)(xright - xleft)/key_cols;
	    /* we divide into columns, then centre in column by considering ratio
	     * of key_left_size to key_right_size
	     * key_size_left/(key_size_left+key_size_right) * (xright-xleft)/key_cols
	     * do one integer division to maximise accuracy (hope we dont overflow !)
	     */
	    xl = xleft + ((xright-xleft)*key_size_left) / (key_cols*(key_size_left+key_size_right));
		yl = yoffset * t->ymax + (key_rows) * key_entry_height + (ktitle_lines+2)*t->v_char;
#endif
	    } else {
 	    	if ( key_vpos == TTOP ) {
 	    	    yl = ytop - (t->v_tic) - t->v_char;
 	        } else {
 	    	    yl = ybot + (t->v_tic) + key_entry_height*key_rows + ktitle_lines*t->v_char;
 	        }
 	        if ( key_hpos == TOUT ) {
		    /* keys outside plot border (right) */
 	    	    xl = xright  + (t->h_tic) + key_size_left;
 	        } else if ( key_hpos == TLEFT ) {
 	    	    xl = xleft  + (t->h_tic) + key_size_left;
 	        } else { 
 	    	    xl = xright  - key_size_right - key_col_wth*(key_cols-1); 
	        }
	   }
	   yl_ref = yl - ktitle_lines * (t->v_char);
 	}
  	if (key == 1) {
  		map_position(&key_user_pos, &xl, &yl, "key");
	}

	if (key && key_box>-3) {
		int yt=yl;
		int yb=yl - key_entry_height*(key_rows-ktitle_lines) - ktitle_lines*t->v_char;
	   int key_xr = xl + key_col_wth * (key_cols-1) + key_size_right;
		/* key_rows seems to contain title at this point ??? */
		(*t->linetype)(key_box);
		(*t->move)(xl-key_size_left,yb);
		(*t->vector)(xl-key_size_left,yt);
		(*t->vector)(key_xr,yt);
		(*t->vector)(key_xr,yb);
		(*t->vector)(xl-key_size_left,yb);
                /* draw a horizontal line between key title and first entry */   /* JFi */
		(*t->move)(xl-key_size_left,yt - (ktitle_lines) * t->v_char);    /* JFi */
                (*t->vector)(xl+key_size_right,yt - (ktitle_lines) * t->v_char); /* JFi */
	}

/* DRAW SURFACES AND CONTOURS */

#ifndef LITE
	if (hidden3d && draw_surface) plot3d_hidden(plots,pcount);
#endif /* not LITE */
	if (key != 0 && strlen(key_title)) {
		sprintf(ss,"%s\n",key_title);
		s = ss;
		yl -= t->v_char/2;
		while( (e=(char *)strchr(s,'\n')) != NULL ) {
			*e = '\0';
			if ( key_just == JLEFT) {
				(*t->justify_text)(LEFT);
				(*t->put_text)(xl+key_text_left,yl,s);
			} else {
				if ((*t->justify_text)(RIGHT)) {
					(*t->put_text)(xl+key_text_right,
						yl,s);
				} else {
					int x=xl+key_text_right-(t->h_char)*strlen(s);
					if (inrange(x,xleft, xright))
						(*t->put_text)(x,yl,s);
				}
			}
			s = ++e;
			yl -= t->v_char;
		}
		yl += t->v_char/2;
	}
	key_count = 0;
	yl_ref = yl -= key_entry_height/2;  /* centralise the keys */

	this_plot = plots;
	for (surface = 0;
	     surface < pcount;
	     this_plot = this_plot->next_sp, surface++) {
#ifndef LITE
		if ( hidden3d )
		    hidden_no_update = FALSE;
#endif /* not LITE */

		if (draw_surface) {
		    (*t->linetype)(this_plot->line_type);
#ifndef LITE
		    if (hidden3d) {
			hidden_line_type_above = this_plot->line_type;
			hidden_line_type_below = this_plot->line_type + 1;
		    }
#endif /* not LITE */		    
		    if (key != 0 && this_plot->title) {
			key_count++;
			key_text(xl, yl, this_plot->title);
		    }
		    
		    switch(this_plot->plot_style) {
				case BOXES: /* can't do boxes in 3d yet so use impulses */
		        case IMPULSES: {
			    if (key != 0 && this_plot->title) {
			    	key_sample_line(xl,yl);
			    }
			    if (!(hidden3d && draw_surface))
			      plot3d_impulses(this_plot);
			    break;
			}
			case LINES: {
			    if (key != 0 && this_plot->title) {
			    	key_sample_line(xl,yl);
 			    }
			    if (!(hidden3d && draw_surface))
			      plot3d_lines(this_plot);
			    break;
			}
                        case YERRORBARS: /* ignored; treat like points */
                        case XERRORBARS: /* ignored; treat like points */
                        case XYERRORBARS: /* ignored; treat like points */
			case POINTSTYLE: {
			    if (key != 0 && this_plot->title 
				&& !clip_point(xl+key_point_offset,yl)) {
					key_sample_point(xl,yl,this_plot->point_type);
			    }
			    if (!(hidden3d && draw_surface))
			      plot3d_points(this_plot);
			    break;
			}
			case LINESPOINTS: {
			    /* put lines */
			    if (key != 0 && this_plot->title) {
			    	key_sample_line(xl,yl);
			    }
 			    if (!(hidden3d && draw_surface))
 			      plot3d_lines(this_plot);
			
			    /* put points */
			    if (key != 0 && this_plot->title 
				&& !clip_point(xl+key_point_offset,yl)) {
				(*t->point)(xl+key_point_offset,yl,
					    this_plot->point_type);
			    }
			    if (!(hidden3d && draw_surface))
			      plot3d_points(this_plot);
			    break;
			}
			case DOTS: {
			    if (key != 0 && this_plot->title) {
				    if (key == 1) {
					 if (!clip_point(xl+key_point_offset,yl)) 
						(*t->point)(xl+key_point_offset,yl, -1);
				    } else {
					(*t->point)(xl+key_point_offset,yl, -1);
				/* (*t->point)(xl+2*(t->h_char),yl, -1); */
				    }
			    }
			    if (!(hidden3d && draw_surface))
			      plot3d_dots(this_plot);
			    break;
			}
		    }
		    if (key != 0 && this_plot->title) {
		    	if ( key_count >= key_rows ) {
				yl = yl_ref;
				xl += key_col_wth;
				key_count = 0;
		    	} else 
		    		yl -= key_entry_height;
		    }
		}

#ifndef LITE
		if ( hidden3d ) {
		    hidden_no_update = TRUE;
		    hidden_line_type_above = this_plot->line_type + (hidden3d ? 2 : 1);
		    hidden_line_type_below = this_plot->line_type + (hidden3d ? 2 : 1);
		}
#endif /* not LITE */

		if (draw_contour && this_plot->contours != NULL) {
			struct gnuplot_contours *cntrs = this_plot->contours;

			(*t->linetype)(this_plot->line_type + (hidden3d ? 2 : 1));
			if (key != 0 && this_plot->title 
			          && !draw_surface && !label_contours) {
			    key_count++;
				key_text(xl,yl, this_plot->title);
				
				switch(this_plot->plot_style) {
					case IMPULSES:
					case LINES:
						key_sample_line(xl,yl);
						break;
                                        case YERRORBARS: /* ignored; treat like points */
                                        case XERRORBARS: /* ignored; treat like points */
                                        case XYERRORBARS: /* ignored; treat like points */
					case POINTSTYLE:
						key_sample_point(xl,yl,this_plot->point_type);
					    break;
  					case LINESPOINTS:
						key_sample_line(xl,yl);
						break;
					case DOTS:
						key_sample_point(xl,yl,-1);
					    break;
				}
			}

			linetypeOffset = this_plot->line_type + (hidden3d ? 2 : 1);
			while (cntrs) {
				if(label_contours && cntrs->isNewLevel) {
				  (*t->linetype)(linetypeOffset++);
				  if (key) {
#ifndef LITE
					if(hidden3d) hidden_line_type_below = hidden_line_type_above = linetypeOffset-1;
#endif /* not LITE */
					key_count++;
					key_text(xl,yl,cntrs->label);
					
 				   	switch(this_plot->plot_style) {
 					   case IMPULSES:
 					   case LINES:
 					   case LINESPOINTS:
 					   key_sample_line(xl,yl);
 						break;
                                           case YERRORBARS: /* ignored; treat like points */
                                           case XERRORBARS: /* ignored; treat like points */
                                           case XYERRORBARS: /* ignored; treat like points */
 					   case POINTSTYLE:
							key_sample_point(xl,yl,this_plot->point_type);
 						   break;
					   case DOTS:
					   	key_sample_point(xl,yl,-1);
						   break;
				   	} /* switch */
				   	
					  	if (key_count >= key_rows ) {
							yl = yl_ref;
							xl += key_col_wth;
							key_count = 0;
						} else  /* might want to use t->v_char if not putting points in key - div */
							yl -= key_entry_height;

				   } /* key */
				} /* label_contours */
				switch(this_plot->plot_style) {
					case IMPULSES:
			   			cntr3d_impulses(cntrs, this_plot);
						break;
					case LINES:
						cntr3d_lines(cntrs);
						break;
                                        case YERRORBARS: /* ignored; treat like points */
                                        case XERRORBARS: /* ignored; treat like points */
                                        case XYERRORBARS: /* ignored; treat like points */
					case POINTSTYLE:
						cntr3d_points(cntrs, this_plot);
						break;
					case LINESPOINTS:
						cntr3d_lines(cntrs);
						cntr3d_points(cntrs, this_plot);
						break;
					case DOTS:
						cntr3d_dots(cntrs);
						break;
				}
				cntrs = cntrs->next;
			}
			if (key != 0 && this_plot->title) {
		    	    if (key_count >= key_rows ) {
				yl = yl_ref;
				xl += key_col_wth;
				key_count = 0;
		    	    } else 
		    		yl -= key_entry_height;
			}
  		}
  	}

	draw_bottom_grid(plots, pcount);

	if (!multiplot) {
		(*t->text)();
		term_graphics = FALSE;
	}
	
	(void) fflush(outfile);

#ifndef LITE
	if (hidden3d && draw_surface) {
	    term_hidden_line_removal();
	    hidden_active = FALSE;
	}
#endif /* not LITE */
}

/* plot3d_impulses:
 * Plot the surfaces in IMPULSES style
 */
static void plot3d_impulses(plot)
	struct surface_points *plot;
{
    int i;				/* point index */
    unsigned int x,y,x0,y0;			/* point in terminal coordinates */
    struct iso_curve *icrvs = plot->iso_crvs;

    while ( icrvs ) {
	struct coordinate GPHUGE *points = icrvs->points;

	for (i = 0; i < icrvs->p_count; i++) {
	  switch (points[i].type)
	    {
	    case INRANGE:
	      {
		map3d_xy(points[i].x, points[i].y, points[i].z, &x, &y);

		if(inrange(0.0, min3d_z, max3d_z)){
		  map3d_xy(points[i].x, points[i].y, 0.0, &x0, &y0);
		} else if(inrange(min3d_z, 0.0, points[i].z)){
		  map3d_xy(points[i].x, points[i].y, min3d_z, &x0, &y0);
		} else {
		  map3d_xy(points[i].x, points[i].y, max3d_z, &x0, &y0);
		}
		
		clip_move(x0,y0);
		clip_vector(x,y);
		
		break;
	      }
	    case OUTRANGE:
	      {
		if(!inrange(points[i].x, x_min3d, x_max3d) ||
		   !inrange(points[i].y, y_min3d, y_max3d))
		  break;		
		
		if(inrange(0.0, min3d_z, max3d_z))
		  {
		    /* zero point is INRANGE */
		    map3d_xy(points[i].x, points[i].y, 0.0, &x0, &y0);

		    /* must cross z=min3d_z or max3d_z limits */
		    if(inrange(min3d_z, 0.0, points[i].z) && 
		       min3d_z != 0.0 && min3d_z != points[i].z)
		      {
			map3d_xy(points[i].x, points[i].y, min3d_z, &x, &y);
		      } 
		    else
		      {
			map3d_xy(points[i].x, points[i].y, max3d_z, &x, &y);
		      }
		  } 
		else 
		  { 
		    /* zero point is also OUTRANGE */ 
		    if(inrange(min3d_z, 0.0, points[i].z) && 
		       inrange(max3d_z, 0.0, points[i].z))
		      {
			/* crosses z=min3d_z or max3d_z limits */
			map3d_xy(points[i].x, points[i].y, max3d_z, &x, &y);
			map3d_xy(points[i].x, points[i].y, min3d_z, &x0, &y0);
		      }
		    else 
		      {
			/* doesn't cross z=min3d_z or max3d_z limits */
			break;
		      }
		  }

		clip_move(x0,y0);
		clip_vector(x,y);
		
		break;
	      }	      
	    default:		/* just a safety */
	    case UNDEFINED: {
	      break;
	    }
	    }
        }

	icrvs = icrvs->next;
      }
}

/* plot3d_lines:
 * Plot the surfaces in LINES style
 */
/* We want to always draw the lines in the same direction, otherwise when
   we draw an adjacent box we might get the line drawn a little differently
   and we get splotches.  */

#ifndef LITE
#endif /* not LITE */

static void plot3d_lines(plot)
	struct surface_points *plot;
{
    int i;
    unsigned int x,y,x0,y0;				/* point in terminal coordinates */
     double clip_x, clip_y, clip_z;
    struct iso_curve *icrvs = plot->iso_crvs;
    struct coordinate GPHUGE *points;
     enum coord_type prev = UNDEFINED;
     double lx[2], ly[2], lz[2];		/* two edge points */

#ifndef LITE
/* These are handled elsewhere.  */
    if (plot->has_grid_topology && hidden3d)
	return;
#endif /* not LITE */

    while (icrvs) {
      prev = UNDEFINED; /* type of previous plot */

	for(i = 0, points = icrvs->points; i < icrvs->p_count; i++) {
	   switch (points[i].type) {
		  case INRANGE: {
		    map3d_xy(points[i].x, points[i].y, points[i].z, &x, &y);
		    
		    if(prev == INRANGE)
		      {
			clip_vector(x, y);
		      }
		    else
		      {
			if(prev == OUTRANGE)			  
			  {
			    /* from outrange to inrange */
			    if (!clip_lines1) {
			      clip_move(x, y);
			    }
			    else {
			      /*
			       * Calculate intersection point and draw vector from there
			       */
			      edge3d_intersect(points, i, &clip_x, &clip_y, &clip_z);
			      
			      map3d_xy(clip_x, clip_y, clip_z, &x0, &y0);
			      
			      clip_move(x0,y0);
			      clip_vector(x,y);
			    }
			  }
			else
			  {
			    clip_move(x,y);
			  }
		      }

		    break;
		  }
		  case OUTRANGE:{
		    if(prev == INRANGE)
		      {
			/* from inrange to outrange */
			if (clip_lines1) {
			  /*
			   * Calculate intersection point and draw vector to it
			   */
			  
			  edge3d_intersect(points, i, &clip_x, &clip_y, &clip_z);
			  
			  map3d_xy(clip_x, clip_y, clip_z, &x0, &y0);
			  
			  clip_vector(x0,y0);
			}
		      }
		    else if (prev == OUTRANGE)
		      {
			/* from outrange to outrange */
			if (clip_lines2) {
			  /*
			   * Calculate the two 3D intersection points if present
			   */
			  if (two_edge3d_intersect(points, i, lx, ly, lz)) {

			    map3d_xy(lx[0], ly[0], lz[0], &x, &y);

			    map3d_xy(lx[1], ly[1], lz[1], &x0, &y0);

			    clip_move(x, y);
			    clip_vector(x0, y0);
			  }
			}
		      }

		    break;
		  }
		  case UNDEFINED: {
		    break;
		  default: graph_error("Unknown point type in plot3d_lines");
		  }
	       }

	   prev = points[i].type;
	 }

	icrvs = icrvs->next;
    }
}

/* plot3d_points:
 * Plot the surfaces in POINTSTYLE style
 */
static void plot3d_points(plot)
	struct surface_points *plot;
{
    int i;
    unsigned x,y;
    struct termentry *t = term;
    struct iso_curve *icrvs = plot->iso_crvs;

    while ( icrvs ) {
	struct coordinate GPHUGE *points = icrvs->points;

	for (i = 0; i < icrvs->p_count; i++) {
	  if (points[i].type == INRANGE) {
	    map3d_xy(points[i].x, points[i].y, points[i].z, &x, &y);

	    if (!clip_point(x,y))
		(*t->point)(x,y, plot->point_type);
	  }
	}

	icrvs = icrvs->next;
    }
}

/* plot3d_dots:
 * Plot the surfaces in DOTS style
 */
static void plot3d_dots(plot)
	struct surface_points *plot;
{
    int i;
    struct termentry *t = term;
    struct iso_curve *icrvs = plot->iso_crvs;

    while ( icrvs ) {
	struct coordinate GPHUGE *points = icrvs->points;

    	for (i = 0; i < icrvs->p_count; i++) {
    		unsigned int x,y;
   	    map3d_xy(points[i].x, points[i].y, points[i].z, &x, &y);

    	    if (!clip_point(x,y))
		(*t->point)(x,y, -1);
    	}

	icrvs = icrvs->next;
    }
}

/* cntr3d_impulses:
 * Plot a surface contour in IMPULSES style
 */
static void cntr3d_impulses(cntr, plot)
	struct gnuplot_contours *cntr;
	struct surface_points *plot;
{
    int i;				/* point index */
    unsigned int x,y,x0,y0;			/* point in terminal coordinates */

    if (draw_contour & CONTOUR_SRF) {
	for (i = 0; i < cntr->num_pts; i++) {
	    map3d_xy(cntr->coords[i].x, cntr->coords[i].y, cntr->coords[i].z,
		     &x, &y);
	    map3d_xy(cntr->coords[i].x, cntr->coords[i].y, base_z,
		     &x0, &y0);

	    clip_move(x0,y0);
	    clip_vector(x,y);
	}
    }
    else
	cntr3d_points(cntr, plot);   /* Must be on base grid, so do points. */
}

/* cntr3d_lines:
 * Plot a surface contour in LINES style
 */
static void cntr3d_lines(cntr)
	struct gnuplot_contours *cntr;
{
    int i;				/* point index */
    unsigned int x,y;				/* point in terminal coordinates */

    if (draw_contour & CONTOUR_SRF) {
	for (i = 0; i < cntr->num_pts; i++) {
    	    map3d_xy(cntr->coords[i].x, cntr->coords[i].y, cntr->coords[i].z,
    		     &x, &y);

 			if (i > 0) {
 				clip_vector(x,y);
 				if(i == 1) suppressMove = TRUE;
 			} else {
 				clip_move(x,y);
 			}
    	}
    }
 	suppressMove = FALSE;  /* beginning a new contour level, so moveto() required */

    if (draw_contour & CONTOUR_BASE) {
	for (i = 0; i < cntr->num_pts; i++) {
    	    map3d_xy(cntr->coords[i].x, cntr->coords[i].y, base_z,
    		     &x, &y);

 			if (i > 0) {
 				clip_vector(x,y);
 				if(i == 1) suppressMove = TRUE;
 			} else {
 				clip_move(x,y);
 			}
 		}
 	}
 	suppressMove = FALSE;  /* beginning a new contour level, so moveto() required */
}

/* cntr3d_points:
 * Plot a surface contour in POINTSTYLE style
 */
static void cntr3d_points(cntr, plot)
	struct gnuplot_contours *cntr;
	struct surface_points *plot;
{
    int i;
    unsigned int x,y;
    struct termentry *t = term;

    if (draw_contour & CONTOUR_SRF) {
	for (i = 0; i < cntr->num_pts; i++) {
    	    map3d_xy(cntr->coords[i].x, cntr->coords[i].y, cntr->coords[i].z,
    		     &x, &y);

	    if (!clip_point(x,y))
		(*t->point)(x,y, plot->point_type);
    	}
    }

    if (draw_contour & CONTOUR_BASE) {
	for (i = 0; i < cntr->num_pts; i++) {
   	    map3d_xy(cntr->coords[i].x, cntr->coords[i].y, base_z,
    		     &x, &y);

	    if (!clip_point(x,y))
		(*t->point)(x,y, plot->point_type);
    	}
    }
}

/* cntr3d_dots:
 * Plot a surface contour in DOTS style
 */
static void cntr3d_dots(cntr)
	struct gnuplot_contours *cntr;
{
    int i;
    unsigned int x,y;
    struct termentry *t = term;

    if (draw_contour & CONTOUR_SRF) {
	for (i = 0; i < cntr->num_pts; i++) {
    	    map3d_xy(cntr->coords[i].x, cntr->coords[i].y, cntr->coords[i].z,
    		     &x, &y);

	    if (!clip_point(x,y))
		(*t->point)(x,y, -1);
    	}
    }

    if (draw_contour & CONTOUR_BASE) {
	for (i = 0; i < cntr->num_pts; i++) {
    	    map3d_xy(cntr->coords[i].x, cntr->coords[i].y, base_z,
    		     &x, &y);

	    if (!clip_point(x,y))
		(*t->point)(x,y, -1);
    	}
    }
}



/* map xmin | xmax to 0 | 1 and same for y
 * 0.1 avoids any rounding errors
 */
#define MAP_HEIGHT_X(x) ( (int) (((x)-x_min3d)/(x_max3d-x_min3d)+0.1) )
#define MAP_HEIGHT_Y(y) ( (int) (((y)-y_min3d)/(y_max3d-y_min3d)+0.1) )

/* if point is at corner, update height[][] and depth[][]
 * we are still assuming that extremes of surfaces are at corners,
 * but we are not assuming order of corners
 */
static void check_corner_height(p,height,depth)
struct coordinate GPHUGE *p;
double height[2][2];
double depth[2][2];
{
	if (p->type != INRANGE) return;
	if ( (fabs(p->x - x_min3d)<zero || fabs(p->x - x_max3d)<zero) &&
	     (fabs(p->y - y_min3d)<zero || fabs(p->y - y_max3d)<zero)) {
		unsigned int x= MAP_HEIGHT_X(p->x);
		unsigned int y= MAP_HEIGHT_Y(p->y);
		if (height[x][y] < p->z)
			height[x][y] = p->z;
		if (depth[x][y] > p->z)
			depth[x][y] = p->z;
	}
}		
		

/* Draw the bottom grid that hold the tic marks for 3d surface. */
static void draw_bottom_grid(plot, plot_num)
	struct surface_points *plot;
	int plot_num;
{
    unsigned int x,y;	/* point in terminal coordinates */
    struct termentry *t = term;
    char ss[MAX_LINE_LEN+1];

	/* work out where the axes and tics are drawn */

	{
		int quadrant=surface_rot_z/90;
		if ( (quadrant+1)&2) {
			zaxis_x = x_max3d;
			xaxis_y = y_max3d;
		} else {
			zaxis_x = x_min3d;
			xaxis_y = y_min3d;
		}

		if (quadrant&2) {
			zaxis_y = y_max3d;
			yaxis_x = x_min3d;
		} else {
			zaxis_y = y_min3d;
			yaxis_x = x_max3d;
		}

		if (surface_rot_x > 90) {
			/* labels on the back axes */
			back_x = yaxis_x = x_min3d + x_max3d - yaxis_x;
			back_y = xaxis_y = y_min3d + y_max3d - xaxis_y;
		} else {
			back_x = x_min3d + x_max3d - yaxis_x;
			back_y = y_min3d + y_max3d - xaxis_y;
		}
	}

    if (draw_border) {
	unsigned int bl_x, bl_y; /* bottom left */
	unsigned int bb_x, bb_y; /* bottom back */
	unsigned int br_x, br_y; /* bottom right */
	unsigned int bf_x, bf_y; /* bottom front */
#ifndef LITE
	int save_update=hidden_no_update;
	hidden_no_update=TRUE;
#endif /* LITE */
	setlinestyle(-2);

	map3d_xy(zaxis_x, zaxis_y, base_z, &bl_x, &bl_y);
	map3d_xy( back_x,  back_y, base_z, &bb_x, &bb_y);
	map3d_xy(x_min3d+x_max3d-zaxis_x, y_min3d+y_max3d-zaxis_y, base_z, &br_x, &br_y);
	map3d_xy(x_min3d+x_max3d-back_x, y_min3d+y_max3d-back_y, base_z, &bf_x, &bf_y);

	/* border around base */
	{
		int save=hidden_active;
		hidden_active=FALSE; /* this is in front */
		if (draw_border&4) draw_clip_line(br_x, br_y, bf_x, bf_y);
		if (draw_border&1) draw_clip_line(bl_x, bl_y, bf_x, bf_y);
		hidden_active=save;
	}
	if (draw_border&2) draw_clip_line(bl_x, bl_y, bb_x, bb_y);
	if (draw_border&8) draw_clip_line(br_x, br_y, bb_x, bb_y);

	if ( draw_surface || (draw_contour&CONTOUR_SRF)) {
		int save=hidden_active;
		/* map the 8 corners to screen */
		unsigned int fl_x, fl_y; /* floor left */
		unsigned int fb_x, fb_y; /* floor back */
		unsigned int fr_x, fr_y; /* floor right */
		unsigned int ff_x, ff_y; /* floor front */

		unsigned int tl_x, tl_y; /* top left */
		unsigned int tb_x, tb_y; /* top back */
		unsigned int tr_x, tr_y; /* top right */
		unsigned int tf_x, tf_y; /* top front */

		map3d_xy(zaxis_x, zaxis_y, floor_z, &fl_x, &fl_y);
		map3d_xy( back_x,  back_y, floor_z, &fb_x, &fb_y);
		map3d_xy(x_min3d+x_max3d-zaxis_x, y_min3d+y_max3d-zaxis_y, floor_z, &fr_x, &fr_y);
		map3d_xy(x_min3d+x_max3d-back_x, y_min3d+y_max3d-back_y, floor_z, &ff_x, &ff_y);

		map3d_xy(zaxis_x, zaxis_y, ceiling_z, &tl_x, &tl_y);
		map3d_xy( back_x,  back_y, ceiling_z, &tb_x, &tb_y);
		map3d_xy(x_min3d+x_max3d-zaxis_x, y_min3d+y_max3d-zaxis_y, ceiling_z, &tr_x, &tr_y);
		map3d_xy(x_min3d+x_max3d-back_x, y_min3d+y_max3d-back_y, ceiling_z, &tf_x, &tf_y);

		/* vertical lines, to surface or to very top */
		if ( (draw_border & 0xf0)==0xf0) {
			/* all four verticals drawn - save some time */
			draw_clip_line(fl_x, fl_y, tl_x, tl_y);
			draw_clip_line(fb_x, fb_y, tb_x, tb_y);
			draw_clip_line(fr_x, fr_y, tr_x, tr_y);
			hidden_active=FALSE; /* this is in front */
			draw_clip_line(ff_x, ff_y, tf_x, tf_y);
			hidden_active=save;
		} else {
		    /* search surfaces for heights at corners */
		    double height[2][2];
		    double depth[2][2];
		    unsigned int zaxis_i=MAP_HEIGHT_X(zaxis_x);
		    unsigned int zaxis_j=MAP_HEIGHT_Y(zaxis_y);
		    unsigned int back_i=MAP_HEIGHT_X(back_x);
		    int back_j=MAP_HEIGHT_Y(back_y);

		    height[0][0]=height[0][1]=height[1][0]=height[1][1]=base_z;
		    depth[0][0]=depth[0][1]=depth[1][0]=depth[1][1]=base_z;
		
		    for (; --plot_num >= 0; plot=plot->next_sp) {
			struct iso_curve *curve=plot->iso_crvs;
			int count=curve->p_count;
			int iso;
			if (plot->plot_type==DATA3D) {
				if (!plot->has_grid_topology) continue;
				iso=plot->num_iso_read;
			} else
				iso=iso_samples_2;
			
			check_corner_height(curve->points, height, depth);
			check_corner_height(curve->points+count-1, height, depth);
			while (--iso)
				curve=curve->next;
			check_corner_height(curve->points, height, depth);
			check_corner_height(curve->points+count-1, height, depth);
		    }

#define VERTICAL(mask, x,y,i,j,bx,by,tx,ty) \
 if (draw_border&mask) \
  draw_clip_line(bx,by,tx,ty);\
else if (height[i][j]!=depth[i][j]) \
{ unsigned int a0,b0, a1, b1; \
  map3d_xy(x,y,depth[i][j],&a0,&b0); \
  map3d_xy(x,y,height[i][j],&a1,&b1); \
  draw_clip_line(a0,b0,a1,b1); \
}

		    VERTICAL(16, zaxis_x, zaxis_y, zaxis_i, zaxis_j,fl_x, fl_y, tl_x, tl_y)
		    VERTICAL(32, back_x, back_y, back_i, back_j,fb_x, fb_y, tb_x, tb_y)
		    VERTICAL(64, x_min3d+x_max3d-zaxis_x, y_min3d+y_max3d-zaxis_y, 1-zaxis_i, 1-zaxis_j,fr_x, fr_y, tr_x, tr_y)
		    hidden_active=FALSE;
		    VERTICAL(128, x_min3d+x_max3d-back_x, y_min3d+y_max3d-back_y, 1-back_i, 1-back_j,ff_x, ff_y, tf_x, tf_y)
		    hidden_active=save;
		}

		/* now border lines on top */
		if (draw_border&256) draw_clip_line(tl_x, tl_y, tb_x, tb_y);
		if (draw_border&512) draw_clip_line(tr_x, tr_y, tb_x, tb_y);
		/* these lines are in front of surface (?) */
		hidden_active=FALSE;
		if (draw_border&1024) draw_clip_line(tl_x, tl_y, tf_x, tf_y);
		if (draw_border&2048) draw_clip_line(tr_x, tr_y, tf_x, tf_y);
		hidden_active=save;
	}
#ifndef LITE
	hidden_no_update=save_update;
#endif /* LITE */
    }

	if (xtics || *xlabel.text) {
		unsigned int x0,y0,x1,y1;
		double mid_x = (x_max3d+x_min3d)/2;
		double len;
		map3d_xy(mid_x, xaxis_y, base_z, &x0, &y0);
		map3d_xy(mid_x, y_min3d + y_max3d - xaxis_y, base_z, &x1, &y1);
		{	/* take care over unsigned quantities */
			int dx = x1-x0;
			int dy = y1-y0;
			len=sqrt((double)(dx*dx+dy*dy));
			tic_unitx = dx/len;
			tic_unity = dy/len;
		}

		if (xtics) {
			gen_tics(FIRST_X_AXIS, &xticdef,grid&(GRID_X|GRID_MX),mxtics,mxtfreq, xtick_callback);
		}

		if (*xlabel.text) {
			/* label at xaxis_y + 1/4 of (xaxis_y-other_y) */
			double step = (2*xaxis_y-y_max3d-y_min3d)/4;
    			map3d_xy(mid_x, xaxis_y+step, base_z,&x1,&y1);
			x1 += xlabel.xoffset * t->h_char;
			y1 += xlabel.yoffset * t->v_char;
			if (!tic_in) {
				x1 -= tic_unitx*ticscale*(t->h_tic);
				y1 -= tic_unity*ticscale*(t->v_tic);
			}
			strcpy(ss, xlabel.text); /* write_multiline mods it */
			write_multiline(x1,y1,ss, CENTRE, JUST_TOP,0, xlabel.font);
		}
	}

	if (ytics || *ylabel.text) {
		unsigned int x0,y0,x1,y1;
		double mid_y = (y_max3d+y_min3d)/2;
		double len;
		map3d_xy(yaxis_x, mid_y, base_z, &x0, &y0);
		map3d_xy(x_min3d + x_max3d - yaxis_x,  mid_y, base_z, &x1, &y1);
		{	/* take care over unsigned quantities */
			int dx = x1-x0;
			int dy = y1-y0;
			len=sqrt((double)(dx*dx+dy*dy));
			tic_unitx = dx/len;
			tic_unity = dy/len;
		}
		if (ytics) {
			gen_tics(FIRST_Y_AXIS, &yticdef,grid&(GRID_Y|GRID_MY),mytics,mytfreq, ytick_callback);
		}

		if (*ylabel.text) {
			double step = (x_max3d+x_min3d-2*yaxis_x)/4;
    			map3d_xy(yaxis_x-step, mid_y, base_z,&x1,&y1);
			x1 += ylabel.xoffset * t->h_char;
			y1 += ylabel.yoffset * t->v_char;
			if (!tic_in) {
				x1 -= tic_unitx*ticscale*(t->h_tic);
				y1 -= tic_unity*ticscale*(t->v_tic);
			}
			strcpy(ss, ylabel.text); /* write_multiline mods it */
			write_multiline(x1,y1,ss, CENTRE, JUST_TOP,0, ylabel.font);
		}
	}
	
	/* do z tics */

	if (ztics && (draw_surface || (draw_contour&CONTOUR_SRF))) {
		gen_tics(FIRST_Z_AXIS, &zticdef, grid&(GRID_Z|GRID_MZ), mztics, mztfreq, ztick_callback);
	}

	if ( (xzeroaxis>=-2) && !is_log_y && inrange(0,y_min3d, y_max3d)) {
		unsigned int x,y,x1,y1;
		(*t->linetype)(xzeroaxis);
		map3d_xy(x_min3d, 0.0, base_z, &x, &y);
		map3d_xy(x_max3d, 0.0, base_z, &x1, &y1);
		draw_clip_line(x,y,x1,y1);
	}
	if ((yzeroaxis>=-2) && !is_log_x && inrange(0,x_min3d, x_max3d)) {
		unsigned int x,y,x1,y1;
		(*t->linetype)(yzeroaxis);
		map3d_xy(0.0, y_min3d, base_z, &x, &y);
		map3d_xy(0.0, y_max3d, base_z, &x1, &y1);
		draw_clip_line(x,y,x1,y1);
	}

/* PLACE ZLABEL - along the middle grid Z axis - eh ? */
    if (*zlabel.text && (draw_surface || (draw_contour & CONTOUR_SRF))) {
    	   map3d_xy(zaxis_x,zaxis_y,z_max3d + (z_max3d-base_z)/4, &x, &y);

	   x += zlabel.xoffset * t->h_char;
	   y += zlabel.yoffset * t->v_char;

	   strcpy(ss, zlabel.text);
	   write_multiline(x,y,ss,CENTRE, CENTRE, 0, zlabel.font);
	   
    }
}


static void xtick_callback(axis,place,text,grid)
int axis;
double place;
char *text;
int grid; /* linetype or -2 for none */
{
	unsigned int x,y,x1,y1;
	double scale=(text?ticscale:miniticscale);
	int dirn=tic_in?1:-1;
	register struct termentry *t = term;
	map3d_xy(place, xaxis_y, base_z, &x, &y);
	if (grid > -2) {
		(*t->linetype)(grid);
		/* to save mapping twice, map non-axis y */
		map3d_xy(place, y_min3d+y_max3d-xaxis_y, base_z, &x1, &y1);
		draw_clip_line(x,y,x1,y1);
		(*t->linetype)(-2);
	}
	if (xtics&TICS_ON_AXIS) {
		map3d_xy(place, (y_min3d+y_max3d)/2, base_z, &x, &y);
	}
	x1=x+tic_unitx*scale*(t->h_tic)*dirn;
	y1=y+tic_unity*scale*(t->v_tic)*dirn;
	draw_clip_line(x,y,x1,y1);
	if (text) {
		int just;
		if (tic_unitx < -0.9)
			just=LEFT;
		else if (tic_unitx < 0.9)
			just=CENTRE;
		else
			just=RIGHT;
		x1=x-tic_unitx*(t->h_tic)*2;
		y1=y-tic_unity*(t->v_tic)*2;
		if (!tic_in) {
			x1 -= tic_unitx*(t->h_tic)*ticscale;
			y1 -= tic_unity*(t->v_tic)*ticscale;
		}
		clip_put_text_just(x1,y1,text,just);
	}
	if (xtics & TICS_MIRROR) {
		map3d_xy(place, y_min3d + y_max3d - xaxis_y, base_z, &x, &y);
		x1=x-tic_unitx*scale*(t->h_tic)*dirn;
		y1=y-tic_unity*scale*(t->v_tic)*dirn;
		draw_clip_line(x,y,x1,y1);
	}
}

static void ytick_callback(axis,place,text,grid)
int axis;
double place;
char *text;
int grid;
{
	unsigned int x,y,x1,y1;
	double scale=(text?ticscale:miniticscale);
	int dirn=tic_in?1:-1;
	register struct termentry *t = term;
	map3d_xy(yaxis_x, place, base_z, &x, &y);
	if (grid > -2) {
		(*t->linetype)(grid);
		map3d_xy(x_min3d+x_max3d-yaxis_x, place, base_z, &x1, &y1);
		draw_clip_line(x,y,x1,y1);
		(*t->linetype)(-2);
	}
	if (ytics&TICS_ON_AXIS) {
		map3d_xy( (x_min3d+x_max3d)/2, place, base_z, &x, &y);
	}
	x1=x+tic_unitx*scale*dirn*(t->h_tic);
	y1=y+tic_unity*scale*dirn*(t->v_tic);
	draw_clip_line(x,y,x1,y1);
	if (text) {
		int just;
		if (tic_unitx < -0.9)
			just=LEFT;
		else if (tic_unitx < 0.9)
			just=CENTRE;
		else
			just=RIGHT;
		x1=x-tic_unitx*(t->h_tic)*2;
		y1=y-tic_unity*(t->v_tic)*2;
		if (!tic_in) {
			x1 -= tic_unitx*(t->h_tic)*ticscale;
			y1 -= tic_unity*(t->v_tic)*ticscale;
		}
		clip_put_text_just(x1,y1,text,just);
	}
	if (ytics & TICS_MIRROR) {
		map3d_xy(x_min3d + x_max3d - yaxis_x, place, base_z, &x, &y);
		x1=x-tic_unitx*scale*(t->h_tic)*dirn;
		y1=y-tic_unity*scale*(t->v_tic)*dirn;
		draw_clip_line(x,y,x1,y1);
	}
}

static void ztick_callback(axis,place,text,grid)
int axis;
double place;
char *text;
int grid;
{
	int len=(text?ticscale:miniticscale)*(tic_in?1:-1)*term->h_tic;
	unsigned int x,y;
	register struct termentry *t = term;
	if (grid > -2) {
		unsigned int x1,y1,x2,y2,x3,y3;
		double other_x=x_min3d+x_max3d-zaxis_x;
		double other_y=y_min3d+y_max3d-zaxis_y;
		(*t->linetype)(grid);
		map3d_xy(zaxis_x, zaxis_y, place, &x1, &y1);
		map3d_xy(back_x, back_y, place, &x2, &y2);
		map3d_xy(other_x, other_y, place, &x3, &y3);
		draw_clip_line(x1,y1,x2,y2);
		draw_clip_line(x2,y2,x3,y3);
		(*t->linetype)(-2);
	}
	map3d_xy(zaxis_x, zaxis_y, place, &x, &y);
	draw_clip_line(x,y,x+len, y);
	if (text) {
		int x1=x-term->h_tic*2;
		if (!tic_in)
			x1 -= term->h_tic*ticscale;
		clip_put_text_just(x1,y,text,RIGHT);
	}
	if (ztics & TICS_MIRROR) {
		double other_x = x_min3d + x_max3d - zaxis_x;
		double other_y = y_min3d + y_max3d - zaxis_y;
		map3d_xy(other_x, other_y, place, &x, &y);
		draw_clip_line(x,y,x-len, y);
	}		
}


static void map_position(pos, x, y, what)
struct position *pos;
unsigned int *x, *y;
char *what;
{

	double xpos=pos->x;
	double ypos=pos->y;
	double zpos=pos->z;
	int screens=0; /* need either 0 or 3 screen co-ordinates */

	switch(pos->scalex) {
		case first_axes:
		case second_axes:
			xpos = LogScale(xpos, is_log_x, log_base_log_x, what, "x");
			break;
		case graph:
			xpos=min_array[FIRST_X_AXIS]+xpos*(max_array[FIRST_X_AXIS]-min_array[FIRST_X_AXIS]);
			break;
		case screen:
			++screens;
	}

	switch(pos->scaley) {
		case first_axes:
		case second_axes:
			ypos = LogScale(ypos, is_log_y, log_base_log_y, what, "y");
			break;
		case graph:
			ypos=min_array[FIRST_Y_AXIS]+ypos*(max_array[FIRST_Y_AXIS]-min_array[FIRST_Y_AXIS]);
			break;
		case screen:
			++screens;
	}

	switch(pos->scalez) {
		case first_axes:
		case second_axes:
			zpos = LogScale(zpos, is_log_z, log_base_log_z, what, "z");
			break;
		case graph:
			zpos=min_array[FIRST_Z_AXIS]+zpos*(max_array[FIRST_Z_AXIS]-min_array[FIRST_Z_AXIS]);
			break;
		case screen:
			++screens;
	}

	if (screens==0) {
			map3d_xy(xpos,ypos,zpos, x,y);
			return;
	}

	if (screens != 3) {
		graph_error("Cannot mix screen co-ordinates with other types");
	}

	{
		register struct termentry *t = term;
		*x = pos->x*(t->xmax) + 0.5;
		*y = pos->y*(t->ymax) + 0.5;
	}

	return;				
}



static void key_text(xl, yl, text)
int xl, yl;
char *text;
{
	if ( key_just == JLEFT && key == -1 ) {
		(*term->justify_text)(LEFT); 
		(*term->put_text)(xl+key_text_left, yl,text);
	} else {
		if ((*term->justify_text)(RIGHT)) {
			if ( key == 1 )
				clip_put_text(xl+key_text_right,yl,text);
			else
				(*term->put_text)(xl+key_text_right,yl,text);
		} else {
			int x=xl+key_text_right-(term->h_char)*strlen(text);
			if (key == 1) {
		  		if (i_inrange(x, xleft, xright))
					clip_put_text(x,yl,text);
			} else {
				(*term->put_text)(x,yl,text);
			}
		}
	}
}

static void key_sample_line(xl, yl)
int xl, yl;
{
	if (key == -1) {
		(*term->move)(xl+key_sample_left,yl);
		(*term->vector)(xl+key_sample_right,yl);
	} else {
		clip_move(xl+key_sample_left,yl);
		clip_vector(xl+key_sample_right,yl);
	}
}

static void key_sample_point(xl, yl, pointtype)
int xl, yl;
int pointtype;
{
	if (!clip_point(xl+key_point_offset,yl)) {
		(*term->point)(xl+key_point_offset,yl, pointtype);
	} else {
		(*term->point)(xl+key_point_offset,yl, pointtype);
	}
}

