/* $Id: plot3d.c,v 1.16 1994/07/20 06:08:36 mjl Exp $
 * $Log: plot3d.c,v $
 * Revision 1.16  1994/07/20  06:08:36  mjl
 * The new 3d functions moved elsewhere.
 *
 * Revision 1.15  1994/07/19  22:13:36  furnish
 * Added new routine pl3poly() for drawing polygons in 3-space, with
 * hidden surface removal.
 *
 * Revision 1.14  1994/07/18  20:33:30  mjl
 * Some more cleaning up.
 *
 * Revision 1.13  1994/07/15  20:37:21  furnish
 * Added routines pl3line and pl3poin for drawing lines and points in 3
 * space.  Added a new example program, and dependency info to build it.
 *
 * Revision 1.12  1994/06/30  18:22:13  mjl
 * All core source files: made another pass to eliminate warnings when using
 * gcc -Wall.  Lots of cleaning up: got rid of includes of math.h or string.h
 * (now included by plplot.h), and other minor changes.  Now each file has
 * global access to the plstream pointer via extern; many accessor functions
 * eliminated as a result.
 *
 * Revision 1.11  1994/05/13  22:56:35  mjl
 * Fixed an old bug -- it was innocuous in the company of the allocation bugs
 * fixed in the last update, which is why it wasn't discovered before now.
 * Now plots correctly as well as frees all memory allocated.
 *
 * Revision 1.10  1994/04/08  12:34:21  mjl
 * Fixed some cases of allocating memory and never freeing it.  Also changed
 * some previously fatal errors into recoverable ones.
 *
 * Revision 1.9  1994/03/23  08:22:00  mjl
 * Cruft elimination.
*/

/*	plot3d.c

	3d plot routines.
*/

#include "plplotP.h"

/* Internal constants */

#define  BINC      50		/* Block size for memory allocation */

static PLINT pl3mode = 0;	/* 0 3d solid; 1 mesh plot */
static PLINT pl3upv = 1;	/* 1 update view; 0 no update */

static PLINT zbflg = 0, zbcol;
static PLFLT zbtck;

static PLINT *oldhiview;
static PLINT *oldloview;
static PLINT *newhiview;
static PLINT *newloview;
static PLINT *u, *v;

static PLINT mhi, xxhi, newhisize;
static PLINT mlo, xxlo, newlosize;

/* Prototypes for static functions */
/* INDENT OFF */

static void plgrid3	(PLFLT);
static void plnxtv	(PLINT *, PLINT *, PLINT, PLINT);
static void plside3	(PLFLT *, PLFLT *, PLFLT **, PLINT, PLINT, PLINT);
static void plt3zz	(PLINT, PLINT, PLINT, PLINT, 
			 PLINT, PLINT *, PLFLT *, PLFLT *, PLFLT **, 
			 PLINT, PLINT, PLINT *, PLINT *);
static void plnxtvhi	(PLINT *, PLINT *, PLINT, PLINT);
static void plnxtvlo	(PLINT *, PLINT *, PLINT, PLINT);
static void savehipoint	(PLINT, PLINT);
static void savelopoint	(PLINT, PLINT);
static void swaphiview	(void);
static void swaploview	(void);
static void myexit	(char *);
static void myabort	(char *);
static void freework	(void);
static int  plabv	(PLINT, PLINT, PLINT, PLINT, PLINT, PLINT);
static void pl3cut	(PLINT, PLINT, PLINT, PLINT, PLINT, 
				PLINT, PLINT, PLINT, PLINT *, PLINT *);
/* INDENT ON */

/*----------------------------------------------------------------------*\
 * void plmesh(x, y, z, nx, ny, opt)
 *
 * Plots a mesh representation of the function z[x][y]. The x values
 * are stored as x[0..nx-1], the y values as y[0..ny-1], and the
 * z values are in the 2-d array z[][]. The integer "opt" specifies:
 *
 *  opt = 1:  Draw lines parallel to x-axis
 *  opt = 2:  Draw lines parallel to y-axis
 *  opt = 3:  Draw lines parallel to both axes
\*----------------------------------------------------------------------*/

void
c_plmesh(PLFLT *x, PLFLT *y, PLFLT **z, PLINT nx, PLINT ny, PLINT opt)
{
    pl3mode = 1;
    plot3d(x, y, z, nx, ny, opt, 0);

    pl3mode = 0;
}

/*----------------------------------------------------------------------*\
 * void plot3d(x, y, z, nx, ny, opt, side)
 *
 * Plots a 3-d representation of the function z[x][y]. The x values
 * are stored as x[0..nx-1], the y values as y[0..ny-1], and the z
 * values are in the 2-d array z[][]. The integer "opt" specifies:
 *
 *  opt = 1:  Draw lines parallel to x-axis
 *  opt = 2:  Draw lines parallel to y-axis
 *  opt = 3:  Draw lines parallel to both axes
\*----------------------------------------------------------------------*/

void
c_plot3d(PLFLT *x, PLFLT *y, PLFLT **z,
	 PLINT nx, PLINT ny, PLINT opt, PLINT side)
{
    PLINT color, font;
    PLFLT cxx, cxy, cyx, cyy, cyz;
    PLINT init, i, ix, iy;

    if (plsc->level < 3) {
	myabort("plot3d: Please set up window first");
	return;
    }

    if (opt < 1 || opt > 3) {
	myabort("plot3d: Bad option");
	return;
    }

    if (nx <= 0 || ny <= 0) {
	myabort("plot3d: Bad array dimensions.");
	return;
    }

/* Check that points in x and in y are strictly increasing */

    for (i = 0; i < nx - 1; i++) {
	if (x[i] >= x[i + 1]) {
	    myabort("plot3d: X array must be strictly increasing");
	    return;
	}
    }
    for (i = 0; i < ny - 1; i++) {
	if (y[i] >= y[i + 1]) {
	    myabort("plot3d: Y array must be strictly increasing");
	    return;
	}
    }

/* Allocate work arrays */

    u = (PLINT *) malloc((size_t) (2 * MAX(nx, ny) * sizeof(PLINT)));
    v = (PLINT *) malloc((size_t) (2 * MAX(nx, ny) * sizeof(PLINT)));
    if ( ! u || ! v)
	myexit("plot3d: Out of memory.");

    plP_gw3wc(&cxx, &cxy, &cyx, &cyy, &cyz);
    init = 1;

/* Call 3d line plotter.  Each viewing quadrant (perpendicular to x-y */
/* plane) must be handled separately. */ 

    if (cxx >= 0.0 && cxy <= 0.0) {
	if (opt == 2) 
	    plt3zz(1, ny, 1, -1, -opt, &init, x, y, z, nx, ny, u, v);
	else {
	    for (iy = 2; iy <= ny; iy++)
		plt3zz(1, iy, 1, -1, -opt, &init, x, y, z, nx, ny, u, v);
	}
	if (opt == 1)
	    plt3zz(1, ny, 1, -1, opt, &init, x, y, z, nx, ny, u, v);
	else {
	    for (ix = 1; ix <= nx - 1; ix++)
		plt3zz(ix, ny, 1, -1, opt, &init, x, y, z, nx, ny, u, v);
	}
    }

    else if (cxx <= 0.0 && cxy <= 0.0) {
	if (opt == 1) 
	    plt3zz(nx, ny, -1, -1, opt, &init, x, y, z, nx, ny, u, v);
	else {
	    for (ix = 2; ix <= nx; ix++) 
		plt3zz(ix, ny, -1, -1, opt, &init, x, y, z, nx, ny, u, v);
	}
	if (opt == 2)
	    plt3zz(nx, ny, -1, -1, -opt, &init, x, y, z, nx, ny, u, v);
	else {
	    for (iy = ny; iy >= 2; iy--)
		plt3zz(nx, iy, -1, -1, -opt, &init, x, y, z, nx, ny, u, v);
	}
    }

    else if (cxx <= 0.0 && cxy >= 0.0) {
	if (opt == 2) 
	    plt3zz(nx, 1, -1, 1, -opt, &init, x, y, z, nx, ny, u, v);
	else {
	    for (iy = ny - 1; iy >= 1; iy--) 
		plt3zz(nx, iy, -1, 1, -opt, &init, x, y, z, nx, ny, u, v);
	}
	if (opt == 1)
	    plt3zz(nx, 1, -1, 1, opt, &init, x, y, z, nx, ny, u, v);
	else {
	    for (ix = nx; ix >= 2; ix--)
		plt3zz(ix, 1, -1, 1, opt, &init, x, y, z, nx, ny, u, v);
	}
    }

    else if (cxx >= 0.0 && cxy >= 0.0) {
	if (opt == 1) 
	    plt3zz(1, 1, 1, 1, opt, &init, x, y, z, nx, ny, u, v);
	else {
	    for (ix = nx - 1; ix >= 1; ix--) 
		plt3zz(ix, 1, 1, 1, opt, &init, x, y, z, nx, ny, u, v);
	}
	if (opt == 2)
	    plt3zz(1, 1, 1, 1, -opt, &init, x, y, z, nx, ny, u, v);
	else {
	    for (iy = 1; iy <= ny - 1; iy++)
		plt3zz(1, iy, 1, 1, -opt, &init, x, y, z, nx, ny, u, v);
	}
    }

/* Finish up by drawing sides, background grid (both are optional) */

    if (side)
	plside3(x, y, z, nx, ny, opt);

    if (zbflg) {
	plP_gatt(&font, &color);
	plcol(zbcol);
	plgrid3(zbtck);
	plcol(color);
    }

    freework();
}

/*----------------------------------------------------------------------*\
 * void plP_gzback()
 *
 * Get background parameters for 3d plot.
\*----------------------------------------------------------------------*/

void
plP_gzback(PLINT **zbf, PLINT **zbc, PLFLT **zbt)
{
    *zbf = &zbflg;
    *zbc = &zbcol;
    *zbt = &zbtck;
}

/*----------------------------------------------------------------------*\
 * void plt3zz()
 *
 * Draws the next zig-zag line for a 3-d plot. The data is stored in array
 * z[][] as a function of x[] and y[]. This is plotted out starting at
 * index (xstar0,ystar0).
 *
 * Depending on the state of "flg0", the sequence of data points sent to
 * plnxtv is altered so as to allow cross-hatch plotting, or plotting
 * parallel to either the x-axis or the y-axis.
\*----------------------------------------------------------------------*/

static void
plt3zz(PLINT xstar0, PLINT ystar0, PLINT dx, PLINT dy, PLINT flg0, PLINT *init,
       PLFLT *x, PLFLT *y, PLFLT **z, PLINT nx, PLINT ny, PLINT *u, PLINT *v)
{
    PLINT flag;
    PLINT n;
    PLINT xstart, ystart;

    n = 0;
    xstart = xstar0;
    ystart = ystar0;
    flag = flg0;

    while (1 <= xstart && xstart <= nx && 1 <= ystart && ystart <= ny) {
	u[n] = plP_wcpcx(plP_w3wcx(
		    x[xstart - 1], y[ystart - 1], z[xstart - 1][ystart - 1]));

	v[n] = plP_wcpcy(plP_w3wcy(
		    x[xstart - 1], y[ystart - 1], z[xstart - 1][ystart - 1]));

	if (flag == -3) {
	    ystart = ystart + dy;
	    flag = -flag;
	}
	else if (flag == -2)
	    ystart = ystart + dy;

	else if (flag == -1) {
	    ystart = ystart + dy;
	    flag = -flag;
	}
	else if (flag == 1)
	    xstart = xstart + dx;

	else if (flag == 2) {
	    xstart = xstart + dx;
	    flag = -flag;
	}
	else if (flag == 3) {
	    xstart = xstart + dx;
	    flag = -flag;
	}
	n = n + 1;
    }

    if (flag == 1 || flag == -2) {
	if (flag == 1) {
	    xstart = xstart - dx;
	    ystart = ystart + dy;
	}
	else if (flag == -2) {
	    ystart = ystart - dy;
	    xstart = xstart + dx;
	}

	if (1 <= xstart && xstart <= nx && 1 <= ystart && ystart <= ny) {
	    u[n] = plP_wcpcx(plP_w3wcx(
		    x[xstart - 1], y[ystart - 1], z[xstart - 1][ystart - 1]));

	    v[n] = plP_wcpcy(plP_w3wcy(
		    x[xstart - 1], y[ystart - 1], z[xstart - 1][ystart - 1]));

	    n = n + 1;
	}
    }
    plnxtv(u, v, n, *init);
    *init = 0;
}

/*----------------------------------------------------------------------*\
 * void plside3()
 *
 * This routine draws sides around the front of the 3d plot so that
 * it does not appear to float.
\*----------------------------------------------------------------------*/

static void
plside3(PLFLT *x, PLFLT *y, PLFLT **z, PLINT nx, PLINT ny, PLINT opt)
{
    PLINT i;
    PLFLT cxx, cxy, cyx, cyy, cyz;
    PLFLT xmin, ymin, zmin, xmax, ymax, zmax, zscale;
    PLFLT tx, ty, ux, uy;

    plP_gw3wc(&cxx, &cxy, &cyx, &cyy, &cyz);
    plP_gdom(&xmin, &xmax, &ymin, &ymax);
    plP_grange(&zscale, &zmin, &zmax);

    if (cxx >= 0.0 && cxy <= 0.0) {
	/* Get x, y coordinates of legs and plot */
	if (opt != 1) {
	    for (i = 0; i < nx; i++) {
		tx = plP_w3wcx(x[i], y[0], zmin);
		ty = plP_w3wcy(x[i], y[0], zmin);
		ux = plP_w3wcx(x[i], y[0], z[i][0]);
		uy = plP_w3wcy(x[i], y[0], z[i][0]);
		pljoin(tx, ty, ux, uy);
	    }
	}

	if (opt != 2) {
	    for (i = 0; i < ny; i++) {
		tx = plP_w3wcx(x[0], y[i], zmin);
		ty = plP_w3wcy(x[0], y[i], zmin);
		ux = plP_w3wcx(x[0], y[i], z[0][i]);
		uy = plP_w3wcy(x[0], y[i], z[0][i]);
		pljoin(tx, ty, ux, uy);
	    }
	}
    }
    else if (cxx <= 0.0 && cxy <= 0.0) {
	if (opt != 1) {
	    for (i = 0; i < nx; i++) {
		tx = plP_w3wcx(x[i], y[ny - 1], zmin);
		ty = plP_w3wcy(x[i], y[ny - 1], zmin);
		ux = plP_w3wcx(x[i], y[ny - 1], z[i][ny - 1]);
		uy = plP_w3wcy(x[i], y[ny - 1], z[i][ny - 1]);
		pljoin(tx, ty, ux, uy);
	    }
	}

	if (opt != 2) {
	    for (i = 0; i < ny; i++) {
		tx = plP_w3wcx(x[0], y[i], zmin);
		ty = plP_w3wcy(x[0], y[i], zmin);
		ux = plP_w3wcx(x[0], y[i], z[0][i]);
		uy = plP_w3wcy(x[0], y[i], z[0][i]);
		pljoin(tx, ty, ux, uy);
	    }
	}
    }
    else if (cxx <= 0.0 && cxy >= 0.0) {
	if (opt != 1) {
	    for (i = 0; i < nx; i++) {
		tx = plP_w3wcx(x[i], y[ny - 1], zmin);
		ty = plP_w3wcy(x[i], y[ny - 1], zmin);
		ux = plP_w3wcx(x[i], y[ny - 1], z[i][ny - 1]);
		uy = plP_w3wcy(x[i], y[ny - 1], z[i][ny - 1]);
		pljoin(tx, ty, ux, uy);
	    }
	}

	if (opt != 2) {
	    for (i = 0; i < ny; i++) {
		tx = plP_w3wcx(x[nx - 1], y[i], zmin);
		ty = plP_w3wcy(x[nx - 1], y[i], zmin);
		ux = plP_w3wcx(x[nx - 1], y[i], z[nx - 1][i]);
		uy = plP_w3wcy(x[nx - 1], y[i], z[nx - 1][i]);
		pljoin(tx, ty, ux, uy);
	    }
	}
    }
    else if (cxx >= 0.0 && cxy >= 0.0) {
	if (opt != 1) {
	    for (i = 0; i < nx; i++) {
		tx = plP_w3wcx(x[i], y[0], zmin);
		ty = plP_w3wcy(x[i], y[0], zmin);
		ux = plP_w3wcx(x[i], y[0], z[i][0]);
		uy = plP_w3wcy(x[i], y[0], z[i][0]);
		pljoin(tx, ty, ux, uy);
	    }
	}

	if (opt != 2) {
	    for (i = 0; i < ny; i++) {
		tx = plP_w3wcx(x[nx - 1], y[i], zmin);
		ty = plP_w3wcy(x[nx - 1], y[i], zmin);
		ux = plP_w3wcx(x[nx - 1], y[i], z[nx - 1][i]);
		uy = plP_w3wcy(x[nx - 1], y[i], z[nx - 1][i]);
		pljoin(tx, ty, ux, uy);
	    }
	}
    }
}

/*----------------------------------------------------------------------*\
 * void plgrid3()
 *
 * This routine draws a grid around the back side of the 3d plot with
 * hidden line removal.
\*----------------------------------------------------------------------*/

static void
plgrid3(PLFLT tick)
{
    PLFLT xmin, ymin, zmin, xmax, ymax, zmax, zscale;
    PLFLT cxx, cxy, cyx, cyy, cyz;
    PLINT u[3], v[3];
    PLINT nsub, prec, mode, digmax, digits, scale;
    PLFLT tp;

    plP_gw3wc(&cxx, &cxy, &cyx, &cyy, &cyz);
    plP_gdom(&xmin, &xmax, &ymin, &ymax);
    plP_grange(&zscale, &zmin, &zmax);

    plgzax(&digmax, &digits);
    nsub = 0;

    pldtik(zmin, zmax, &tick, &nsub, &mode, &prec, digmax, &scale);
    tp = tick * floor(zmin / tick) + tick;
    pl3upv = 0;

    if (cxx >= 0.0 && cxy <= 0.0) {
	while (tp <= zmax) {
	    u[0] = plP_wcpcx(plP_w3wcx(xmin, ymax, tp));
	    v[0] = plP_wcpcy(plP_w3wcy(xmin, ymax, tp));
	    u[1] = plP_wcpcx(plP_w3wcx(xmax, ymax, tp));
	    v[1] = plP_wcpcy(plP_w3wcy(xmax, ymax, tp));
	    u[2] = plP_wcpcx(plP_w3wcx(xmax, ymin, tp));
	    v[2] = plP_wcpcy(plP_w3wcy(xmax, ymin, tp));
	    plnxtv(u, v, 3, 0);

	    tp += tick;
	}
	u[0] = plP_wcpcx(plP_w3wcx(xmax, ymax, zmin));
	v[0] = plP_wcpcy(plP_w3wcy(xmax, ymax, zmin));
	u[1] = plP_wcpcx(plP_w3wcx(xmax, ymax, zmax));
	v[1] = plP_wcpcy(plP_w3wcy(xmax, ymax, zmax));
	plnxtv(u, v, 2, 0);
    }
    else if (cxx <= 0.0 && cxy <= 0.0) {
	while (tp <= zmax) {
	    u[0] = plP_wcpcx(plP_w3wcx(xmax, ymax, tp));
	    v[0] = plP_wcpcy(plP_w3wcy(xmax, ymax, tp));
	    u[1] = plP_wcpcx(plP_w3wcx(xmax, ymin, tp));
	    v[1] = plP_wcpcy(plP_w3wcy(xmax, ymin, tp));
	    u[2] = plP_wcpcx(plP_w3wcx(xmin, ymin, tp));
	    v[2] = plP_wcpcy(plP_w3wcy(xmin, ymin, tp));
	    plnxtv(u, v, 3, 0);

	    tp += tick;
	}
	u[0] = plP_wcpcx(plP_w3wcx(xmax, ymin, zmin));
	v[0] = plP_wcpcy(plP_w3wcy(xmax, ymin, zmin));
	u[1] = plP_wcpcx(plP_w3wcx(xmax, ymin, zmax));
	v[1] = plP_wcpcy(plP_w3wcy(xmax, ymin, zmax));
	plnxtv(u, v, 2, 0);
    }
    else if (cxx <= 0.0 && cxy >= 0.0) {
	while (tp <= zmax) {
	    u[0] = plP_wcpcx(plP_w3wcx(xmax, ymin, tp));
	    v[0] = plP_wcpcy(plP_w3wcy(xmax, ymin, tp));
	    u[1] = plP_wcpcx(plP_w3wcx(xmin, ymin, tp));
	    v[1] = plP_wcpcy(plP_w3wcy(xmin, ymin, tp));
	    u[2] = plP_wcpcx(plP_w3wcx(xmin, ymax, tp));
	    v[2] = plP_wcpcy(plP_w3wcy(xmin, ymax, tp));
	    plnxtv(u, v, 3, 0);

	    tp += tick;
	}
	u[0] = plP_wcpcx(plP_w3wcx(xmin, ymin, zmin));
	v[0] = plP_wcpcy(plP_w3wcy(xmin, ymin, zmin));
	u[1] = plP_wcpcx(plP_w3wcx(xmin, ymin, zmax));
	v[1] = plP_wcpcy(plP_w3wcy(xmin, ymin, zmax));
	plnxtv(u, v, 2, 0);
    }
    else if (cxx >= 0.0 && cxy >= 0.0) {
	while (tp <= zmax) {
	    u[0] = plP_wcpcx(plP_w3wcx(xmin, ymin, tp));
	    v[0] = plP_wcpcy(plP_w3wcy(xmin, ymin, tp));
	    u[1] = plP_wcpcx(plP_w3wcx(xmin, ymax, tp));
	    v[1] = plP_wcpcy(plP_w3wcy(xmin, ymax, tp));
	    u[2] = plP_wcpcx(plP_w3wcx(xmax, ymax, tp));
	    v[2] = plP_wcpcy(plP_w3wcy(xmax, ymax, tp));
	    plnxtv(u, v, 3, 0);

	    tp += tick;
	}
	u[0] = plP_wcpcx(plP_w3wcx(xmin, ymax, zmin));
	v[0] = plP_wcpcy(plP_w3wcy(xmin, ymax, zmin));
	u[1] = plP_wcpcx(plP_w3wcx(xmin, ymax, zmax));
	v[1] = plP_wcpcy(plP_w3wcy(xmin, ymax, zmax));
	plnxtv(u, v, 2, 0);
    }
    pl3upv = 1;
}

/*----------------------------------------------------------------------*\
 * void plnxtv()
 *
 * Draw the next view of a 3-d plot. The physical coordinates of the
 * points for the next view are placed in the n points of arrays u and
 * v. The silhouette found so far is stored in the heap as a set of m peak
 * points.
 *
 * These routines dynamically allocate memory for hidden line removal.
 * Memory is allocated in blocks of 2*BINC*sizeof(PLINT) bytes.  Large
 * values of BINC give better performance but also allocate more memory
 * than is needed. If your 3D plots are very "spiky" or you are working
 * with very large matrices then you will probably want to increase BINC.
\*----------------------------------------------------------------------*/

static void
plnxtv(PLINT *u, PLINT *v, PLINT n, PLINT init)
{
    plnxtvhi(u, v, n, init);

    if (pl3mode)
	plnxtvlo(u, v, n, init);
}

/*----------------------------------------------------------------------*\
 * void plnxtvhi()
 *
 * Draw the top side of the 3-d plot.
\*----------------------------------------------------------------------*/

static void
plnxtvhi(PLINT *u, PLINT *v, PLINT n, PLINT init)
{
    PLINT i, j, first;
    PLINT sx1 = 0, sx2 = 0, sy1 = 0, sy2 = 0;
    PLINT su1, su2, sv1, sv2;
    PLINT cx, cy, px, py;
    PLINT seg, ptold, lstold = 0, pthi, pnewhi, newhi, change, ochange = 0;

    first = 1;
    pnewhi = 0;

/*
 * For the initial set of points, just display them and store them as the
 * peak points.
 */
    if (init == 1) {

	oldhiview = (PLINT *) malloc((size_t) (2 * n * sizeof(PLINT)));
	if ( ! oldhiview)
	    myexit("plnxtvhi: Out of memory.");

	plP_movphy(u[0], v[0]);
	oldhiview[0] = u[0];
	oldhiview[1] = v[0];
	for (i = 1; i < n; i++) {
	    plP_draphy(u[i], v[i]);
	    oldhiview[2 * i] = u[i];
	    oldhiview[2 * i + 1] = v[i];
	}
	mhi = n;
	return;
    }

/*
 * Otherwise, we need to consider hidden-line removal problem. We scan
 * over the points in both the old (i.e. oldhiview[]) and new (i.e. u[]
 * and v[]) arrays in order of increasing x coordinate.  At each stage, we
 * find the line segment in the other array (if one exists) that straddles
 * the x coordinate of the point. We have to determine if the point lies
 * above or below the line segment, and to check if the below/above status
 * has changed since the last point.
 *
 * If pl3upv = 0 we do not update the view, this is useful for drawing
 * lines on the graph after we are done plotting points.  Hidden line
 * removal is still done, but the view is not updated.
 */
    xxhi = 0;
    i = 0;
    j = 0;
    if (pl3upv != 0) {
	newhisize = 2 * (mhi + BINC);
	if (newhiview != NULL) {
	    newhiview = 
		(PLINT *) realloc((void *) newhiview,
				  (size_t) (newhisize * sizeof(PLINT)));
	}
	else {
	    newhiview = 
		(PLINT *) malloc((size_t) (newhisize * sizeof(PLINT)));
	}
	if ( ! newhiview)
	    myexit("plnxtvhi: Out of memory.");
    }

/*
 * (oldhiview[2*i], oldhiview[2*i]) is the i'th point in the old array
 * (u[j], v[j]) is the j'th point in the new array
 */
    while (i < mhi || j < n) {

/*
 * The coordinates of the point under consideration are (px,py).  The line
 * segment joins (sx1,sy1) to (sx2,sy2).  "ptold" is true if the point
 * lies in the old array. We set it by comparing the x coordinates of the
 * i'th old point and the j'th new point, being careful if we have fallen
 * past the edges. Having found the point, load up the point and segment
 * coordinates appropriately.
 */
	ptold = ((oldhiview[2 * i] < u[j] && i < mhi) || j >= n);
	if (ptold) {
	    px = oldhiview[2 * i];
	    py = oldhiview[2 * i + 1];
	    seg = j > 0 && j < n;
	    if (seg) {
		sx1 = u[j - 1];
		sy1 = v[j - 1];
		sx2 = u[j];
		sy2 = v[j];
	    }
	}
	else {
	    px = u[j];
	    py = v[j];
	    seg = i > 0 && i < mhi;
	    if (seg) {
		sx1 = oldhiview[2 * (i - 1)];
		sy1 = oldhiview[2 * (i - 1) + 1];
		sx2 = oldhiview[2 * i];
		sy2 = oldhiview[2 * i + 1];
	    }
	}

/*
 * Now determine if the point is higher than the segment, using the
 * logical function "above". We also need to know if it is the old view or
 * the new view that is higher. "newhi" is set true if the new view is
 * higher than the old.
 */
	if (seg)
	    pthi = plabv(px, py, sx1, sy1, sx2, sy2);
	else
	    pthi = 1;

	newhi = (ptold && !pthi) || (!ptold && pthi);
	change = (newhi && !pnewhi) || (!newhi && pnewhi);

/*
 * There is a new intersection point to put in the peak array if the state
 * of "newhi" changes.
 */
	if (first) {
	    plP_movphy(px, py);
	    first = 0;
	    lstold = ptold;
	    savehipoint(px, py);
	    pthi = 0;
	    ochange = 0;
	}
	else if (change) {

/*
 * Take care of special cases at end of arrays.  If pl3upv is 0 the
 * endpoints are not connected to the old view.
 */
	    if (pl3upv == 0 && ((!ptold && j == 0) || (ptold && i == 0))) {
		plP_movphy(px, py);
		lstold = ptold;
		pthi = 0;
		ochange = 0;
	    }
	    else if (pl3upv == 0 &&
		     (( ! ptold && i >= mhi) || (ptold && j >= n))) {
		plP_movphy(px, py);
		lstold = ptold;
		pthi = 0;
		ochange = 0;
	    }

/*
 * If pl3upv is not 0 then we do want to connect the current line with the
 * previous view at the endpoints.  Also find intersection point with old
 * view.
 */
	    else {
		if (i == 0) {
		    sx1 = oldhiview[0];
		    sy1 = -1;
		    sx2 = oldhiview[0];
		    sy2 = oldhiview[1];
		}
		else if (i >= mhi) {
		    sx1 = oldhiview[2 * (mhi - 1)];
		    sy1 = oldhiview[2 * (mhi - 1) + 1];
		    sx2 = oldhiview[2 * (mhi - 1)];
		    sy2 = -1;
		}
		else {
		    sx1 = oldhiview[2 * (i - 1)];
		    sy1 = oldhiview[2 * (i - 1) + 1];
		    sx2 = oldhiview[2 * i];
		    sy2 = oldhiview[2 * i + 1];
		}

		if (j == 0) {
		    su1 = u[0];
		    sv1 = -1;
		    su2 = u[0];
		    sv2 = v[0];
		}
		else if (j >= n) {
		    su1 = u[n - 1];
		    sv1 = v[n - 1];
		    su2 = u[n];
		    sv2 = -1;
		}
		else {
		    su1 = u[j - 1];
		    sv1 = v[j - 1];
		    su2 = u[j];
		    sv2 = v[j];
		}

/* Determine the intersection */

		pl3cut(sx1, sy1, sx2, sy2, su1, sv1, su2, sv2, &cx, &cy);
		if (cx == px && cy == py) {
		    if (lstold && !ochange)
			plP_movphy(px, py);
		    else
			plP_draphy(px, py);

		    savehipoint(px, py);
		    lstold = 1;
		    pthi = 0;
		}
		else {
		    if (lstold && !ochange)
			plP_movphy(cx, cy);
		    else
			plP_draphy(cx, cy);

		    lstold = 1;
		    savehipoint(cx, cy);
		}
		ochange = 1;
	    }
	}

/* If point is high then draw plot to point and update view. */

	if (pthi) {
	    if (lstold && ptold)
		plP_movphy(px, py);
	    else
		plP_draphy(px, py);

	    savehipoint(px, py);
	    lstold = ptold;
	    ochange = 0;
	}

	pnewhi = newhi;

	if (ptold)
	    i = i + 1;
	else
	    j = j + 1;
    }

/* Set oldhiview */

    swaphiview();
}

/*----------------------------------------------------------------------*\
 * void plnxtvlo()
 *
 * Draw the bottom side of the 3-d plot.
\*----------------------------------------------------------------------*/

static void
plnxtvlo(PLINT *u, PLINT *v, PLINT n, PLINT init)
{
    PLINT i, j, first;
    PLINT sx1 = 0, sx2 = 0, sy1 = 0, sy2 = 0;
    PLINT su1, su2, sv1, sv2;
    PLINT cx, cy, px, py;
    PLINT seg, ptold, lstold = 0, ptlo, pnewlo, newlo, change, ochange = 0;

    first = 1;
    pnewlo = 0;

/*
 * For the initial set of points, just display them and store them as the
 * peak points.
 */
    if (init == 1) {

	oldloview = (PLINT *) malloc((size_t) (2 * n * sizeof(PLINT)));
	if ( ! oldloview)
	    myexit("plnxtvlo: Out of memory.");

	plP_movphy(u[0], v[0]);
	oldloview[0] = u[0];
	oldloview[1] = v[0];
	for (i = 1; i < n; i++) {
	    plP_draphy(u[i], v[i]);
	    oldloview[2 * i] = u[i];
	    oldloview[2 * i + 1] = v[i];
	}
	mlo = n;
	return;
    }

/*
 * Otherwise, we need to consider hidden-line removal problem. We scan
 * over the points in both the old (i.e. oldloview[]) and new (i.e. u[]
 * and v[]) arrays in order of increasing x coordinate.  At each stage, we
 * find the line segment in the other array (if one exists) that straddles
 * the x coordinate of the point. We have to determine if the point lies
 * above or below the line segment, and to check if the below/above status
 * has changed since the last point.
 *
 * If pl3upv = 0 we do not update the view, this is useful for drawing
 * lines on the graph after we are done plotting points.  Hidden line
 * removal is still done, but the view is not updated.
 */
    xxlo = 0;
    i = 0;
    j = 0;
    if (pl3upv != 0) {
	newlosize = 2 * (mlo + BINC);
	if (newloview != NULL) {
	    newloview = 
		(PLINT *) realloc((void *) newloview,
				  (size_t) (newlosize * sizeof(PLINT)));
	}
	else {
	    newloview = 
		(PLINT *) malloc((size_t) (newlosize * sizeof(PLINT)));
	}
	if ( ! newloview)
	    myexit("plnxtvlo: Out of memory.");
    }

/*
 * (oldloview[2*i], oldloview[2*i]) is the i'th point in the old array
 * (u[j], v[j]) is the j'th point in the new array.
 */
    while (i < mlo || j < n) {

/*
 * The coordinates of the point under consideration are (px,py).  The line
 * segment joins (sx1,sy1) to (sx2,sy2).  "ptold" is true if the point
 * lies in the old array. We set it by comparing the x coordinates of the
 * i'th old point and the j'th new point, being careful if we have fallen
 * past the edges. Having found the point, load up the point and segment
 * coordinates appropriately.
 */
	ptold = ((oldloview[2 * i] < u[j] && i < mlo) || j >= n);
	if (ptold) {
	    px = oldloview[2 * i];
	    py = oldloview[2 * i + 1];
	    seg = j > 0 && j < n;
	    if (seg) {
		sx1 = u[j - 1];
		sy1 = v[j - 1];
		sx2 = u[j];
		sy2 = v[j];
	    }
	}
	else {
	    px = u[j];
	    py = v[j];
	    seg = i > 0 && i < mlo;
	    if (seg) {
		sx1 = oldloview[2 * (i - 1)];
		sy1 = oldloview[2 * (i - 1) + 1];
		sx2 = oldloview[2 * i];
		sy2 = oldloview[2 * i + 1];
	    }
	}

/*
 * Now determine if the point is lower than the segment, using the logical
 * function "above". We also need to know if it is the old view or the new
 * view that is lower. "newlo" is set true if the new view is lower than
 * the old.
 */
	if (seg)
	    ptlo = !plabv(px, py, sx1, sy1, sx2, sy2);
	else
	    ptlo = 1;

	newlo = (ptold && !ptlo) || (!ptold && ptlo);
	change = (newlo && !pnewlo) || (!newlo && pnewlo);

/*
 * There is a new intersection point to put in the peak array if the state
 * of "newlo" changes.
 */
	if (first) {
	    plP_movphy(px, py);
	    first = 0;
	    lstold = ptold;
	    savelopoint(px, py);
	    ptlo = 0;
	    ochange = 0;
	}
	else if (change) {

/*
 * Take care of special cases at end of arrays.  If pl3upv is 0 the
 * endpoints are not connected to the old view.
 */
	    if (pl3upv == 0 && ((!ptold && j == 0) || (ptold && i == 0))) {
		plP_movphy(px, py);
		lstold = ptold;
		ptlo = 0;
		ochange = 0;
	    }
	    else if (pl3upv == 0 &&
		     (( ! ptold && i >= mlo) || (ptold && j >= n))) {
		plP_movphy(px, py);
		lstold = ptold;
		ptlo = 0;
		ochange = 0;
	    }

/*
 * If pl3upv is not 0 then we do want to connect the current line with the
 * previous view at the endpoints.  Also find intersection point with old
 * view.
 */
	    else {
		if (i == 0) {
		    sx1 = oldloview[0];
		    sy1 = 100000;
		    sx2 = oldloview[0];
		    sy2 = oldloview[1];
		}
		else if (i >= mlo) {
		    sx1 = oldloview[2 * (mlo - 1)];
		    sy1 = oldloview[2 * (mlo - 1) + 1];
		    sx2 = oldloview[2 * (mlo - 1)];
		    sy2 = 100000;
		}
		else {
		    sx1 = oldloview[2 * (i - 1)];
		    sy1 = oldloview[2 * (i - 1) + 1];
		    sx2 = oldloview[2 * i];
		    sy2 = oldloview[2 * i + 1];
		}

		if (j == 0) {
		    su1 = u[0];
		    sv1 = 100000;
		    su2 = u[0];
		    sv2 = v[0];
		}
		else if (j >= n) {
		    su1 = u[n - 1];
		    sv1 = v[n - 1];
		    su2 = u[n];
		    sv2 = 100000;
		}
		else {
		    su1 = u[j - 1];
		    sv1 = v[j - 1];
		    su2 = u[j];
		    sv2 = v[j];
		}

/* Determine the intersection */

		pl3cut(sx1, sy1, sx2, sy2, su1, sv1, su2, sv2, &cx, &cy);
		if (cx == px && cy == py) {
		    if (lstold && !ochange)
			plP_movphy(px, py);
		    else
			plP_draphy(px, py);

		    savelopoint(px, py);
		    lstold = 1;
		    ptlo = 0;
		}
		else {
		    if (lstold && !ochange)
			plP_movphy(cx, cy);
		    else
			plP_draphy(cx, cy);

		    lstold = 1;
		    savelopoint(cx, cy);
		}
		ochange = 1;
	    }
	}

/* If point is low then draw plot to point and update view. */

	if (ptlo) {
	    if (lstold && ptold)
		plP_movphy(px, py);
	    else
		plP_draphy(px, py);

	    savelopoint(px, py);
	    lstold = ptold;
	    ochange = 0;
	}

	pnewlo = newlo;

	if (ptold)
	    i = i + 1;
	else
	    j = j + 1;
    }

/* Set oldloview */

    swaploview();
}

/*----------------------------------------------------------------------*\
 * savehipoint
 * savelopoint
 *
 * Add a point to the list of currently visible peaks/valleys, when
 * drawing the top/bottom surface, respectively.
\*----------------------------------------------------------------------*/

static void
savehipoint(PLINT px, PLINT py)
{
    if (pl3upv == 0)
	return;

    if (xxhi >= newhisize) {	/* allocate additional space */
	newhisize += 2 * BINC;
	newhiview = (PLINT *) realloc((void *) newhiview,
				      (size_t) (newhisize * sizeof(PLINT)));
	if ( ! newhiview) 
	    myexit("savehipoint: Out of memory.");
    }

    newhiview[xxhi] = px;
    xxhi++;
    newhiview[xxhi] = py;
    xxhi++;
}

static void
savelopoint(PLINT px, PLINT py)
{
    if (pl3upv == 0)
	return;

    if (xxlo >= newlosize) {	/* allocate additional space */
	newlosize += 2 * BINC;
	newloview = (PLINT *) realloc((void *) newloview,
				      (size_t) (newlosize * sizeof(PLINT)));
	if ( ! newloview)
	    myexit("savelopoint: Out of memory.");
    }

    newloview[xxlo] = px;
    xxlo++;
    newloview[xxlo] = py;
    xxlo++;
}

/*----------------------------------------------------------------------*\
 * swaphiview
 * swaploview
 *
 * Swaps the top/bottom views.  Need to do a real swap so that the 
 * memory cleanup routine really frees everything (and only once).
\*----------------------------------------------------------------------*/

static void
swaphiview(void)
{
    PLINT *tmp;

    if (pl3upv != 0) {
	mhi = xxhi / 2;
	tmp = oldhiview;
	oldhiview = newhiview;
	newhiview = tmp;
    }
}

static void
swaploview(void)
{
    PLINT *tmp;

    if (pl3upv != 0) {
	mlo = xxlo / 2;
	tmp = oldloview;
	oldloview = newloview;
	newloview = tmp;
    }
}

/*----------------------------------------------------------------------*\
 * freework
 *
 * Frees memory associated with work arrays
\*----------------------------------------------------------------------*/

static void
freework(void)
{
    free_mem(oldhiview);
    free_mem(oldloview);
    free_mem(newhiview);
    free_mem(newloview);
    free_mem(v);
    free_mem(u);
}

/*----------------------------------------------------------------------*\
 * myexit
 *
 * Calls plexit, cleaning up first.
\*----------------------------------------------------------------------*/

static void
myexit(char *msg)
{
    freework();
    plexit(msg);
}

/*----------------------------------------------------------------------*\
 * myabort
 *
 * Calls plabort, cleaning up first.
 * Caller should return to the user program.
\*----------------------------------------------------------------------*/

static void
myabort(char *msg)
{
    freework();
    plabort(msg);
}

/*----------------------------------------------------------------------*\
 * int plabv()
 *
 * Determines if point (px,py) lies above the line joining (sx1,sy1) to
 * (sx2,sy2). It only works correctly if sx1 <= px <= sx2.
\*----------------------------------------------------------------------*/

static int
plabv(PLINT px, PLINT py, PLINT sx1, PLINT sy1, PLINT sx2, PLINT sy2)
{
    PLINT above;

    if (py >= sy1 && py >= sy2)
	above = 1;
    else if (py < sy1 && py < sy2)
	above = 0;
    else if ((PLFLT) (sx2 - sx1) * (py - sy1) >
	     (PLFLT) (px - sx1) * (sy2 - sy1))
	above = 1;
    else
	above = 0;

    return ((PLINT) above);
}

/*----------------------------------------------------------------------*\
 * void pl3cut()
 *
 * Determines the point of intersection (cx,cy) between the line joining
 * (sx1,sy1) to (sx2,sy2) and the line joining (su1,sv1) to (su2,sv2).
\*----------------------------------------------------------------------*/

static void
pl3cut(PLINT sx1, PLINT sy1, PLINT sx2, PLINT sy2,
       PLINT su1, PLINT sv1, PLINT su2, PLINT sv2, PLINT *cx, PLINT *cy)
{
    PLINT x21, y21, u21, v21, yv1, xu1, a, b;
    PLFLT fa, fb;

    x21 = sx2 - sx1;
    y21 = sy2 - sy1;
    u21 = su2 - su1;
    v21 = sv2 - sv1;
    yv1 = sy1 - sv1;
    xu1 = sx1 - su1;

    a = x21 * v21 - y21 * u21;
    fa = (PLFLT) a;
    if (a == 0) {
	if (sx2 < su2) {
	    *cx = sx2;
	    *cy = sy2;
	}
	else {
	    *cx = su2;
	    *cy = sv2;
	}
	return;
    }
    else {
	b = yv1 * u21 - xu1 * v21;
	fb = (PLFLT) b;
	*cx = (PLINT) (sx1 + (fb * x21) / fa + .5);
	*cy = (PLINT) (sy1 + (fb * y21) / fa + .5);
    }
}
