Initial release of Maemo 5 port of gnuplot
[gnuplot] / src / hidden3d.c
diff --git a/src/hidden3d.c b/src/hidden3d.c
new file mode 100644 (file)
index 0000000..0bb4ef1
--- /dev/null
@@ -0,0 +1,2490 @@
+#ifndef lint
+static char *RCSid() { return RCSid("$Id: hidden3d.c,v 1.56.2.3 2008/06/02 19:40:10 sfeam Exp $"); }
+#endif
+
+/* GNUPLOT - hidden3d.c */
+
+/*[
+ * Copyright 1986 - 1993, 1998, 1999, 2004   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 complete modified source code.  Modifications are to
+ * be distributed as patches to the released version.  Permission to
+ * distribute binaries produced by compiling modified sources is granted,
+ * provided you
+ *   1. distribute the corresponding source modifications from the
+ *    released version in the form of a patch file along with the binaries,
+ *   2. add special version identification to distinguish your version
+ *    in addition to the base release version number,
+ *   3. provide your name and address as the primary contact for the
+ *    support of your modified version, and
+ *   4. retain our contact information in regard to use of the base
+ *    software.
+ * Permission to distribute the released version of the source code along
+ * with corresponding source modifications in the form of a patch file is
+ * granted with same provisions 2 through 4 for binary distributions.
+ *
+ * This software is provided "as is" without express or implied warranty
+ * to the extent permitted by applicable law.
+]*/
+
+
+/*
+ * 1999 Hans-Bernhard Broeker (Broeker@physik.rwth-aachen.de)
+ *
+ * Major rewrite, affecting just about everything
+ *
+ */
+
+#include "hidden3d.h"
+#include "color.h"
+#include "pm3d.h"
+#include "alloc.h"
+#include "axis.h"
+#include "command.h"
+#include "dynarray.h"
+#include "graph3d.h"
+#include "tables.h"
+#include "term_api.h"
+#include "util.h"
+#include "util3d.h"
+
+
+/*************************/
+/* Configuration section */
+/*************************/
+
+/* If this module is compiled with HIDDEN3D_GRIDBOX = 1 defined, it
+ * will store the information about {x|y}{min|max} in an other
+ * (additional!) form: a bit mask, with each bit representing one
+ * horizontal or vertical strip of the screen. The bits for strips a
+ * polygon spans are set to one. This allows to test for xy overlap
+ * of an edge with a polygon simply by comparing bit patterns.  */
+#ifndef HIDDEN3D_GRIDBOX
+#define HIDDEN3D_GRIDBOX 0
+#endif
+
+/* HBB 19991204: new code started to finally implement a spatially
+ * ordered data structure to store the polygons in. This is meant to
+ * speed up the HLR process. Before, the hot spot of hidden3d was the
+ * loop in in_front, where by far most of the polygons are rejected by
+ * the first test, already. The idea is to _not_ to loop over all
+ * those polygons far away from the edge under consideration, in the
+ * first place. Instead, store the polygons in an xy grid of lists,
+ * so we can select a sample of these lists to test a given edge
+ * against. */
+#ifndef HIDDEN3D_QUADTREE
+#define HIDDEN3D_QUADTREE 0
+#endif
+#if HIDDEN3D_QUADTREE && HIDDEN3D_GRIDBOX
+# warning HIDDEN3D_QUADTREE & HIDDEN3D_GRIDBOX do not work together, sensibly!
+#endif
+
+/* If you don't want the color-distinction between the
+ * 'top' and 'bottom' sides of the surface, like I do, then just compile
+ * with -DBACKSIDE_LINETYPE_OFFSET = 0. */
+#ifndef BACKSIDE_LINETYPE_OFFSET
+# define BACKSIDE_LINETYPE_OFFSET 1
+#endif
+
+/* This #define lets you choose if the diagonals that
+ * divide each original quadrangle in two triangles will be drawn
+ * visible or not: do draw them, define it to be 7L, otherwise let be
+ * 3L */
+#ifndef TRIANGLE_LINESDRAWN_PATTERN
+# define TRIANGLE_LINESDRAWN_PATTERN 3L
+#endif
+
+/* Handle out-of-range or undefined points. Compares the maximum
+ * marking (0=inrange, 1=outrange, 2=undefined) of the coordinates of
+ * a vertex to the value #defined here. If not less, the vertex is
+ * rejected, and all edges that hit it, as well. NB: if this is set to
+ * anything above 1, gnuplot may crash with a floating point exception
+ * in hidden3d. You get what you asked for ... */
+#ifndef HANDLE_UNDEFINED_POINTS
+# define HANDLE_UNDEFINED_POINTS 1
+#endif
+/* Symbolic value for 'do not handle Undefined Points specially' */
+#define UNHANDLED (UNDEFINED+1)
+
+/* If both subtriangles of a quad were cancelled, try if using the
+ * other diagonal is better. This only makes a difference if exactly
+ * one vertex of the quad is unusable, and that one is on the 'usual'
+ * tried diagonal. In such a case, the border of the hole in the
+ * surface will be less rough than with the previous method, as the
+ * border follows the undefined region as close as it can. */
+#ifndef SHOW_ALTERNATIVE_DIAGONAL
+# define SHOW_ALTERNATIVE_DIAGONAL 1
+#endif
+
+/* If the two triangles in a quad are both drawn, and they show
+ * different sides to the user (the quad is 'bent over'), then it's
+ * preferrable to force the diagonal being visible to avoid other
+ * parts of the scene being obscured by a line the user can't
+ * see. This avoids unnecessary user surprises. */
+#ifndef HANDLE_BENTOVER_QUADRANGLES
+# define HANDLE_BENTOVER_QUADRANGLES 1
+#endif
+
+/* The actual configuration is stored in these variables, modifiable
+ * at runtime through 'set hidden3d' options */
+static int hiddenBacksideLinetypeOffset = BACKSIDE_LINETYPE_OFFSET;
+static long hiddenTriangleLinesdrawnPattern = TRIANGLE_LINESDRAWN_PATTERN;
+static int hiddenHandleUndefinedPoints = HANDLE_UNDEFINED_POINTS;
+static int hiddenShowAlternativeDiagonal = SHOW_ALTERNATIVE_DIAGONAL;
+static int hiddenHandleBentoverQuadrangles = HANDLE_BENTOVER_QUADRANGLES;
+
+
+/**************************************************************/
+/**************************************************************
+ * The 'real' code begins, here.                              *
+ *                                                            *
+ * first: types and global variables                          *
+ **************************************************************/
+/**************************************************************/
+
+/* precision of calculations in normalized space. Coordinates closer to
+ * each other than an absolute difference of EPSILON are considered
+ * equal, by some of the routines in this module. */
+#define EPSILON 1e-5
+
+/* The code used to die messily if the scale parameters got over-large.
+ * Prevent this from happening due to mousing by locking out the mouse
+ * response. */
+TBOOLEAN disable_mouse_z = FALSE;
+
+/* Some inexact operations: == , > , >=, sign() */
+#define EQ(X,Y)  (fabs( (X) - (Y) ) < EPSILON) /* X == Y */
+#define GR(X,Y)  ((X) >  (Y) + EPSILON)                /* X >  Y */
+#define GE(X,Y)  ((X) >= (Y) - EPSILON)                /* X >= Y */
+#define SIGN(X)  ( ((X)<-EPSILON) ? -1: ((X)>EPSILON) )
+
+/* A plane equation, stored as a four-element vector. The equation
+ * itself is: x*p[0]+y*p[1]+z*p[2]+1*p[3]=0 */
+typedef coordval t_plane[4];
+
+/* One edge of the mesh. The edges are (currently) organized into a
+ * linked list as a method of traversing them back-to-front. */
+typedef struct edge {
+    long v1, v2;               /* the vertices at either end */
+    int style;                 /* linetype index */
+    struct lp_style_type *lp;  /* line/point style attributes */
+    long next;                 /* index of next edge in z-sorted list */
+} edge;
+typedef edge GPHUGE *p_edge;
+
+
+/* One polygon (actually: always a triangle) of the surface
+ * mesh(es). */
+#define POLY_NVERT 3
+typedef struct polygon {
+    long vertex[POLY_NVERT];    /* The vertices (indices on vlist) */
+    /* min/max in all three directions */
+    coordval xmin, xmax, ymin, ymax, zmin, zmax;
+    t_plane plane;             /* the plane coefficients */
+    TBOOLEAN frontfacing;      /* is polygon facing front- or backwards? */
+#if ! HIDDEN3D_QUADTREE
+    long next;                 /* index of next polygon in z-sorted list */
+#endif
+#if HIDDEN3D_GRIDBOX
+    unsigned long xbits;       /* x coverage mask of bounding box */
+    unsigned long ybits;       /* y coverage mask of bounding box */
+#endif
+} polygon;
+typedef polygon GPHUGE *p_polygon;
+
+#if HIDDEN3D_GRIDBOX
+# define UINT_BITS (CHAR_BIT * sizeof(unsigned int))
+# define COORD_TO_BITMASK(x,shift)                                                     \
+  (~0U << (unsigned int) ((((x) / surface_scale) + 1.0) / 2.0 * UINT_BITS + (shift)))
+# define CALC_BITRANGE(range_min, range_max)                            \
+  ((~COORD_TO_BITMASK((range_max), 1)) & COORD_TO_BITMASK(range_min, 0))
+#endif
+
+/* Enumeration of possible types of line, for use with the
+ * store_edge() function. Influences the position in the grid the
+ * second vertex will be put to, relative to the one that is passed
+ * in, as another argument to that function. edir_none is for
+ * single-pixel 'blind' edges, which exist only to facilitate output
+ * of 'points' style splots.
+ *
+ * Directions are interpreted in a pseudo-geographical coordinate
+ * system of the data grid: within the isoline, we count from left to
+ * right (west to east), and the isolines themselves are counted from
+ * top to bottom, described as north and south. */
+typedef enum edge_direction {
+    edir_west, edir_north,
+    edir_NW, edir_NE,
+    edir_impulse, edir_point,
+    edir_vector
+} edge_direction;
+
+/* direction into which the polygon is facing (the corner with the
+ * right angle, inside the mesh, that is). The reference identifiying
+ * the whole cell is always the lower right, i.e. southeast one. */
+typedef enum polygon_direction {
+    pdir_NE, pdir_SE, pdir_SW, pdir_NW
+} polygon_direction;
+
+/* Three dynamical arrays that describe what we have to plot: */
+static dynarray vertices, edges, polygons;
+
+/* convenience #defines to make the generic vector useable as typed arrays */
+#define vlist ((p_vertex) vertices.v)
+#define plist ((p_polygon) polygons.v)
+#define elist ((p_edge) edges.v)
+
+static long pfirst;            /* first polygon in zsorted chain*/
+static long efirst;            /* first edges in zsorted chain */
+
+#if HIDDEN3D_QUADTREE
+/* HBB 20000716: spatially oriented hierarchical data structure to
+ * store polygons in. For now, it's a simple xy grid of z-sorted
+ * lists. A single polygon can appear in several lists, if it spans
+ * cell borders */
+typedef struct qtreelist {
+    long p;                    /* the polygon */
+    long next;                 /* next element in this chain */
+} qtreelist;
+typedef qtreelist GPHUGE *p_qtreelist;
+
+/* the number of cells in x and y direction: */
+# ifndef QUADTREE_GRANULARITY
+#  define QUADTREE_GRANULARITY 10
+# endif
+/* indices of the heads of all the cells' chains: */
+static long quadtree[QUADTREE_GRANULARITY][QUADTREE_GRANULARITY];
+
+/* and a routine to calculate the cells' position in that array: */
+static int
+coord_to_treecell(coordval x)
+{
+    int index;
+    index = ((((x) / surface_scale) + 1.0) / 2.0) * QUADTREE_GRANULARITY;
+    if (index >= QUADTREE_GRANULARITY)
+       index = QUADTREE_GRANULARITY - 1;
+    else if (index < 0)
+       index = 0;
+
+    return index;
+}
+
+/* the dynarray to actually store all that stuff in: */
+static dynarray qtree;
+#define qlist ((p_qtreelist) qtree.v)
+#endif /* HIDDEN3D_QUADTREE*/
+
+/* Prototypes for internal functions of this module. */
+static long int store_vertex __PROTO((struct coordinate GPHUGE *point,
+                                     lp_style_type *lp_style, TBOOLEAN color_from_column));
+static long int make_edge __PROTO((long int vnum1, long int vnum2,
+                                  struct lp_style_type *lp,
+                                  int style, int next));
+static long int store_edge __PROTO((long int vnum1, edge_direction direction,
+                                   long int crvlen, struct lp_style_type *lp,
+                                   int style));
+static GP_INLINE double eval_plane_equation __PROTO((t_plane p, p_vertex v));
+static long int store_polygon __PROTO((long int vnum1,
+                                      polygon_direction direction,
+                                      long int crvlen));
+static void color_edges __PROTO((long int new_edge, long int old_edge,
+                                long int new_poly, long int old_poly,
+                                int style_above, int style_below));
+static void build_networks __PROTO((struct surface_points * plots,
+                                   int pcount));
+int compare_edges_by_zmin __PROTO((SORTFUNC_ARGS p1, SORTFUNC_ARGS p2));
+int compare_polys_by_zmax __PROTO((SORTFUNC_ARGS p1, SORTFUNC_ARGS p2));
+static void sort_edges_by_z __PROTO((void));
+static void sort_polys_by_z __PROTO((void));
+static TBOOLEAN get_plane __PROTO((p_polygon p, t_plane plane));
+static long split_line_at_ratio __PROTO((long int vnum1, long int vnum2, double w));
+static GP_INLINE double area2D __PROTO((p_vertex v1, p_vertex v2,
+                                       p_vertex v3));
+static void draw_vertex __PROTO((p_vertex v));
+static GP_INLINE void draw_edge __PROTO((p_edge e, p_vertex v1, p_vertex v2));
+static GP_INLINE void handle_edge_fragment __PROTO((long int edgenum,
+                                                   long int vnum1,
+                                                   long int vnum2,
+                                                   long int firstpoly));
+static int in_front __PROTO((long int edgenum,
+                            long int vnum1, long int vnum2,
+                            long int *firstpoly));
+
+
+/* Set the options for hidden3d. To be called from set.c, when the
+ * user has begun a command with 'set hidden3d', to parse the rest of
+ * that command */
+void
+set_hidden3doptions()
+{
+    struct value t;
+    int tmp;
+
+    while (!END_OF_COMMAND) {
+       switch (lookup_table(&set_hidden3d_tbl[0], c_token)) {
+       case S_HI_DEFAULTS:
+           /* reset all parameters to defaults */
+           reset_hidden3doptions();
+           c_token++;
+           if (!END_OF_COMMAND)
+               int_error(c_token,
+                         "No further options allowed after 'defaults'");
+           return;
+           break;
+       case S_HI_OFFSET:
+           c_token++;
+           hiddenBacksideLinetypeOffset = (int) real(const_express(&t));
+           c_token--;
+           break;
+       case S_HI_NOOFFSET:
+           hiddenBacksideLinetypeOffset = 0;
+           break;
+       case S_HI_TRIANGLEPATTERN:
+           c_token++;
+           hiddenTriangleLinesdrawnPattern = (int) real(const_express(&t));
+           c_token--;
+           break;
+       case S_HI_UNDEFINED:
+           c_token++;
+           tmp = (int) real(const_express(&t));
+           if (tmp <= 0 || tmp > UNHANDLED)
+               tmp = UNHANDLED;
+           hiddenHandleUndefinedPoints = tmp;
+           c_token--;
+           break;
+       case S_HI_NOUNDEFINED:
+           hiddenHandleUndefinedPoints = UNHANDLED;
+           break;
+       case S_HI_ALTDIAGONAL:
+           hiddenShowAlternativeDiagonal = 1;
+           break;
+       case S_HI_NOALTDIAGONAL:
+           hiddenShowAlternativeDiagonal = 0;
+           break;
+       case S_HI_BENTOVER:
+           hiddenHandleBentoverQuadrangles = 1;
+           break;
+       case S_HI_NOBENTOVER:
+           hiddenHandleBentoverQuadrangles = 0;
+           break;
+       case S_HI_INVALID:
+           int_error(c_token, "No such option to hidden3d (or wrong order)");
+       default:
+           break;
+       }
+       c_token++;
+    }
+}
+
+void
+show_hidden3doptions()
+{
+    fprintf(stderr,"\
+\t  Back side of surfaces has linestyle offset of %d\n\
+\t  Bit-Mask of Lines to draw in each triangle is %ld\n\
+\t  %d: ",
+           hiddenBacksideLinetypeOffset, hiddenTriangleLinesdrawnPattern,
+           hiddenHandleUndefinedPoints);
+
+    switch (hiddenHandleUndefinedPoints) {
+    case OUTRANGE:
+       fputs("Outranged and undefined datapoints are omitted from the surface.\n",
+             stderr);
+       break;
+    case UNDEFINED:
+       fputs("Only undefined datapoints are omitted from the surface.\n",
+             stderr);
+       break;
+    case UNHANDLED:
+       fputs("Will not check for undefined datapoints (may cause crashes).\n",
+             stderr);
+       break;
+    default:
+       fputs("Value stored for undefined datapoint handling is illegal!!!\n",
+             stderr);
+       break;
+    }
+
+    fprintf(stderr,"\
+\t  Will %suse other diagonal if it gives a less jaggy outline\n\
+\t  Will %sdraw diagonal visibly if quadrangle is 'bent over'\n",
+           hiddenShowAlternativeDiagonal ? "" : "not ",
+           hiddenHandleBentoverQuadrangles ? "" : "not ");
+}
+
+/* Implements proper 'save'ing of the new hidden3d options... */
+void
+save_hidden3doptions(FILE *fp)
+{
+    if (!hidden3d) {
+       fputs("unset hidden3d\n", fp);
+       return;
+    }
+    fprintf(fp, "set hidden3d offset %d trianglepattern %ld undefined %d %saltdiagonal %sbentover\n",
+           hiddenBacksideLinetypeOffset,
+           hiddenTriangleLinesdrawnPattern,
+           hiddenHandleUndefinedPoints,
+           hiddenShowAlternativeDiagonal ? "" : "no",
+           hiddenHandleBentoverQuadrangles ? "" : "no");
+}
+
+/* Initialize the necessary steps for hidden line removal and
+   initialize global variables. */
+void
+init_hidden_line_removal()
+{
+    /* Check for some necessary conditions to be set elsewhere: */
+    /* HandleUndefinedPoints mechanism depends on these: */
+    assert(OUTRANGE == 1);
+    assert(UNDEFINED == 2);
+
+    /* Re-mapping of this value makes the test easier in the critical
+     * section */
+    if (hiddenHandleUndefinedPoints < OUTRANGE)
+       hiddenHandleUndefinedPoints = UNHANDLED;
+
+    init_dynarray(&vertices, sizeof(vertex), 100, 100);
+    init_dynarray(&edges, sizeof(edge), 100, 100);
+    init_dynarray(&polygons, sizeof(polygon), 100, 100);
+#if HIDDEN3D_QUADTREE
+    init_dynarray(&qtree, sizeof(qtreelist), 100, 100);
+#endif
+
+}
+
+/* Reset the hidden line data to a fresh start. */
+void
+reset_hidden_line_removal()
+{
+    vertices.end = 0;
+    edges.end = 0;
+    polygons.end = 0;
+#if HIDDEN3D_QUADTREE
+    qtree.end = 0;
+#endif
+}
+
+
+/* Terminates the hidden line removal process.                  */
+/* Free any memory allocated by init_hidden_line_removal above. */
+void
+term_hidden_line_removal()
+{
+    free_dynarray(&polygons);
+    free_dynarray(&edges);
+    free_dynarray(&vertices);
+#if HIDDEN3D_QUADTREE
+    free_dynarray(&qtree);
+#endif
+}
+
+
+#if 0 /* UNUSED ! */
+/* Do we see the top or bottom of the polygon, or is it 'on edge'? */
+#define GET_SIDE(vlst,csign)                                           \
+do {                                                                   \
+    double ctmp =                                                      \
+       vlist[vlst[0]].x * (vlist[vlst[1]].y - vlist[vlst[2]].y) +      \
+       vlist[vlst[1]].x * (vlist[vlst[2]].y - vlist[vlst[0]].y) +      \
+       vlist[vlst[2]].x * (vlist[vlst[0]].y - vlist[vlst[1]].y);       \
+    csign = SIGN (ctmp);                                               \
+} while (0)
+#endif /* UNUSED */
+
+static long int
+store_vertex (
+    struct coordinate GPHUGE * point,
+    lp_style_type *lp_style,
+    TBOOLEAN color_from_column)
+{
+    p_vertex thisvert = nextfrom_dynarray(&vertices);
+
+    thisvert->lp_style = lp_style;
+    if ((int) point->type >= hiddenHandleUndefinedPoints) {
+       FLAG_VERTEX_AS_UNDEFINED(*thisvert);
+       return (-1);
+    }
+    map3d_xyz(point->x, point->y, point->z, thisvert);
+    if (color_from_column) {
+       thisvert->real_z = point->CRD_COLOR;
+       thisvert->lp_style->pm3d_color.lt = LT_COLORFROMCOLUMN;
+    } else
+       thisvert->real_z = point->z;
+
+#ifdef HIDDEN3D_VAR_PTSIZE
+    /* Store pointer back to original point */
+    /* Needed to support variable pointsize */
+    thisvert->original = point;
+#endif
+       
+    return (thisvert - vlist);
+}
+
+/* A part of store_edge that does the actual storing. Used by
+ * in_front(), as well, so I separated it out. */
+static long int
+make_edge(
+    long vnum1, long vnum2,
+    struct lp_style_type *lp,
+    int style,
+    int next)
+{
+    p_edge thisedge = nextfrom_dynarray(&edges);
+    p_vertex v1 = vlist + vnum1;
+    p_vertex v2 = vlist + vnum2;
+
+    /* ensure z ordering inside each edge */
+    if (v1->z >= v2->z) {
+       thisedge->v1 = vnum1;
+       thisedge->v2 = vnum2;
+    } else {
+       thisedge->v1 = vnum2;
+       thisedge->v2 = vnum1;
+    }
+
+    thisedge->style = style;
+    thisedge->lp = lp;
+    thisedge->next = next;
+
+    return thisedge - elist;
+}
+
+/* store the edge from vnum1 to vnum2 into the edge list. Ensure that
+ * the vertex with higher z is stored in v1, to ease sorting by zmax */
+static long int
+store_edge(
+    long int vnum1,
+    edge_direction direction,
+    long int crvlen,
+    struct lp_style_type *lp,
+    int style)
+{
+    p_vertex v1 = vlist + vnum1;
+    p_vertex v2 = NULL;                /* just in case: initialize... */
+    long int vnum2;
+    unsigned int drawbits = (0x1 << direction);
+
+    switch (direction) {
+    case edir_vector:
+       v2 = v1 + 1;
+       drawbits = 0;
+       break;
+    case edir_west:
+       v2 = v1 - 1;
+       break;
+    case edir_north:
+       v2 = v1 - crvlen;
+       break;
+    case edir_NW:
+       v2 = v1 - crvlen - 1;
+       break;
+    case edir_NE:
+       v2 = v1 - crvlen;
+       v1 -= 1;
+       drawbits >>= 1;         /* altDiag is handled like normal NW one */
+       break;
+    case edir_impulse:
+       v2 = v1 - 1;
+       drawbits = 0;           /* don't care about the triangle pattern */
+       break;
+    case edir_point:
+       v2 = v1;
+       drawbits = 0;           /* nothing to draw, but disable check */
+       break;
+    }
+
+    vnum2 = v2 - vlist;
+
+    if (VERTEX_IS_UNDEFINED(*v1)
+       || VERTEX_IS_UNDEFINED(*v2)) {
+       return -2;
+    }
+
+    if (drawbits &&            /* no bits set: 'blind' edge --> no test! */
+       ! (hiddenTriangleLinesdrawnPattern & drawbits)
+       )
+       style = -3;
+
+    return make_edge(vnum1, vnum2, lp, style, -1);
+}
+
+
+/* Calculate the normal equation coefficients of the plane of polygon
+ * 'p'. Uses is the 'signed projected area' method. Its benefit is
+ * that it doesn't rely on only three of the vertices of 'p', as the
+ * naive cross product method does. */
+static TBOOLEAN
+get_plane(p_polygon poly, t_plane plane)
+{
+    int i;
+    p_vertex v1, v2;
+    double x, y, z, s;
+    TBOOLEAN frontfacing=TRUE;
+
+    /* calculate the signed areas of the polygon projected onto the
+     * planes x=0, y=0 and z=0, respectively. The three areas form
+     * the components of the plane's normal vector: */
+    v1 = vlist + poly->vertex[POLY_NVERT - 1];
+    v2 = vlist + poly->vertex[0];
+    plane[0] = (v1->y - v2->y) * (v1->z + v2->z);
+    plane[1] = (v1->z - v2->z) * (v1->x + v2->x);
+    plane[2] = (v1->x - v2->x) * (v1->y + v2->y);
+    for (i = 1; i < POLY_NVERT; i++) {
+       v1 = v2;
+       v2 = vlist + poly->vertex[i];
+       plane[0] += (v1->y - v2->y) * (v1->z + v2->z);
+       plane[1] += (v1->z - v2->z) * (v1->x + v2->x);
+       plane[2] += (v1->x - v2->x) * (v1->y + v2->y);
+    }
+
+    /* Normalize the resulting normal vector */
+    s = sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
+
+    if (GE(0.0, s)) {
+       /* The normal vanishes, i.e. the polygon is degenerate. We build
+        * another vector that is orthogonal to the line of the polygon */
+       v1 = vlist + poly->vertex[0];
+       for (i = 1; i < POLY_NVERT; i++) {
+           v2 = vlist + poly->vertex[i];
+           if (!V_EQUAL(v1, v2))
+               break;
+       }
+
+       /* build (x,y,z) that should be linear-independant from <v1, v2> */
+       x = v1->x;
+       y = v1->y;
+       z = v1->z;
+       if (EQ(y, v2->y))
+           y += 1.0;
+       else
+           x += 1.0;
+
+       /* Re-do the signed area computations */
+       plane[0] = v1->y * (v2->z - z) + v2->y * (z - v1->z) + y * (v1->z - v2->z);
+       plane[1] = v1->z * (v2->x - x) + v2->z * (x - v1->x) + z * (v1->x - v2->x);
+       plane[2] = v1->x * (v2->y - y) + v2->x * (y - v1->y) + x * (v1->y - v2->y);
+       s = sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
+    }
+
+    /* ensure that normalized c is > 0 */
+    if (plane[2] < 0.0) {
+       s *= -1.0;
+       frontfacing = FALSE;
+    }
+
+    plane[0] /= s;
+    plane[1] /= s;
+    plane[2] /= s;
+
+    /* Now we have the normalized normal vector, insert one of the
+     * vertices into the equation to get 'd'. For an even better result,
+     * an average over all the vertices might be used */
+    plane[3] = -plane[0] * v1->x - plane[1] * v1->y - plane[2] * v1->z;
+
+    return frontfacing;
+}
+
+
+/* Evaluate the plane equation represented a four-vector for the given
+ * vector. For points in the plane, this should result in values ==0.
+ * < 0 is 'away' from the polygon, > 0 is infront of it */
+static GP_INLINE double
+eval_plane_equation(t_plane p, p_vertex v)
+{
+    return (p[0]*v->x + p[1]*v->y + p[2]*v->z + p[3]);
+}
+
+
+/* Build the data structure for this polygon. The return value is the
+ * index of the newly generated polygon. This is memorized for access
+ * to polygons in the previous isoline, from the next-following
+ * one. */
+static long int
+store_polygon(long vnum1, polygon_direction direction, long crvlen)
+{
+    long int v[POLY_NVERT];
+    p_vertex v1, v2, v3;
+    p_polygon p;
+
+    switch (direction) {
+    case pdir_NE:
+       v[0] = vnum1;
+       v[2] = vnum1 - crvlen;
+       v[1] = v[2] - 1;
+       break;
+    case pdir_SW:
+       /* triangle points southwest, here */
+       v[0] = vnum1;
+       v[1] = vnum1 - 1;
+       v[2] = v[1] - crvlen;
+       break;
+    case pdir_SE:
+       /* alt-diagonal, case 1: southeast triangle: */
+       v[0] = vnum1;
+       v[2] = vnum1 - crvlen;
+       v[1] = vnum1 - 1;
+       break;
+    case pdir_NW:
+       v[2] = vnum1 - crvlen;
+       v[0] = vnum1 - 1;
+       v[1] = v[0] - crvlen;
+       break;
+    }
+
+    v1 = vlist + v[0];
+    v2 = vlist + v[1];
+    v3 = vlist + v[2];
+
+    if (VERTEX_IS_UNDEFINED(*v1)
+       || VERTEX_IS_UNDEFINED(*v2)
+       || VERTEX_IS_UNDEFINED(*v3)
+       )
+       return (-2);
+
+    /* Check if polygon is degenerate */
+    if (V_EQUAL(v1,v2) || V_EQUAL(v2,v3) || V_EQUAL(v3,v1))
+       return (-2);
+
+    /* All else OK, fill in the polygon: */
+
+    p = nextfrom_dynarray(&polygons);
+
+    memcpy (p->vertex, v, sizeof(v));
+#if ! HIDDEN3D_QUADTREE
+    p->next = -1;
+#endif
+
+    /* Some helper macros for repeated code blocks: */
+
+    /* Gets Minimum 'var' value of polygon 'poly' into variable
+     * 'min. C is one of x, y, or z: */
+#define GET_MIN(poly, var, min)                        \
+    do {                                       \
+       int i;                                  \
+       long *v = poly->vertex;                 \
+                                               \
+       min = vlist[*v++].var;                  \
+       for (i = 1; i< POLY_NVERT; i++, v++)    \
+           if (vlist[*v].var < min)            \
+               min = vlist[*v].var;            \
+        if (min < -surface_scale) disable_mouse_z = TRUE;      \
+    } while (0)
+
+    /* Gets Maximum 'var' value of polygon 'poly', as with GET_MIN */
+#define GET_MAX(poly, var, max)                        \
+    do {                                       \
+       int i;                                  \
+       long *v = poly->vertex;                 \
+                                               \
+       max = vlist[*v++].var;                  \
+       for (i = 1; i< POLY_NVERT; i++, v++)    \
+           if (vlist[*v].var > max)            \
+               max = vlist[*v].var;            \
+        if (max > surface_scale) disable_mouse_z = TRUE;       \
+    } while (0)
+
+    GET_MIN(p, x, p->xmin);
+    GET_MIN(p, y, p->ymin);
+    GET_MIN(p, z, p->zmin);
+    GET_MAX(p, x, p->xmax);
+    GET_MAX(p, y, p->ymax);
+    GET_MAX(p, z, p->zmax);
+#undef GET_MIN
+#undef GET_MAX
+
+#if HIDDEN3D_GRIDBOX
+    p->xbits = CALC_BITRANGE(p->xmin, p->xmax);
+    p->ybits = CALC_BITRANGE(p->ymin, p->ymax);
+#endif
+
+    p->frontfacing = get_plane(p, p->plane);
+
+    return (p - plist);
+}
+
+/* color edges, based on the orientation of polygon(s). One of the two
+ * edges passed in is a new one, meaning there is no other polygon
+ * sharing it, yet. The other, 'old' edge is common to the new polygon
+ * and another one, which was created earlier on. If these two polygon
+ * differ in their orientation (one front-, the other backsided to the
+ * viewer), this routine has to resolve that conflict.  Edge colours
+ * are changed only if the edge wasn't invisible, before */
+static void
+color_edges(
+    long int new_edge,         /* index of 'new', conflictless edge */
+    long int old_edge,         /* index of 'old' edge, may conflict */
+    long int new_poly,         /* index of current polygon */
+    long int old_poly,         /* index of poly sharing old_edge */
+    int above,                 /* style number for front of polygons */
+    int below)                 /* style number for backside of polys */
+{
+    int casenumber;
+
+    if (new_poly > -2) {
+       /* new polygon was built successfully */
+       if (old_poly <= -2)
+           /* old polygon doesn't exist. Use new_polygon for both: */
+           old_poly = new_poly;
+
+       casenumber =
+           (plist[new_poly].frontfacing ? 1 : 0)
+           + 2 * (plist[old_poly].frontfacing ? 1 : 0);
+       switch (casenumber) {
+       case 0:
+           /* both backfacing */
+           if (elist[new_edge].style >= -2)
+               elist[new_edge].style   = below;
+           if (elist[old_edge].style >= -2)
+               elist[old_edge].style = below;
+           break;
+       case 2:
+           if (elist[new_edge].style >= -2)
+               elist[new_edge].style = below;
+           /* FALLTHROUGH */
+       case 1:
+           /* new front-, old one backfacing, or */
+           /* new back-, old one frontfacing */
+           if (((new_edge == old_edge)
+                && hiddenHandleBentoverQuadrangles) /* a diagonal edge! */
+               || (elist[old_edge].style >= -2)) {
+               /* conflict has occured: two polygons meet here, with opposige
+                * sides being shown. What's to do?
+                * 1) find a vertex of one polygon outside this common
+                * edge
+                * 2) check wether it's in front of or behind the
+                * other polygon's plane
+                * 3) if in front, color the edge accoring to the
+                * vertex' polygon, otherwise, color like the other
+                * polygon */
+               long int vnum1 = elist[old_edge].v1;
+               long int vnum2 = elist[old_edge].v2;
+               p_polygon p = plist + new_poly;
+               long int pvert = -1;
+               double point_to_plane;
+
+               if (p->vertex[0] == vnum1) {
+                   if (p->vertex[1] == vnum2) {
+                       pvert = p->vertex[2];
+                   } else if (p->vertex[2] == vnum2) {
+                       pvert = p->vertex[1];
+                   }
+               } else if (p->vertex[1] == vnum1) {
+                   if (p->vertex[0] == vnum2) {
+                       pvert = p->vertex[2];
+                   } else if (p->vertex[2] == vnum2) {
+                       pvert = p->vertex[0];
+                   }
+               } else if (p->vertex[2] == vnum1) {
+                   if (p->vertex[0] == vnum2) {
+                       pvert = p->vertex[1];
+                   } else if (p->vertex[1] == vnum2) {
+                       pvert = p->vertex[0];
+                   }
+               }
+               assert (pvert >= 0);
+
+               point_to_plane =
+                   eval_plane_equation(plist[old_poly].plane, vlist + pvert);
+
+               if (point_to_plane > 0) {
+                   /* point in new_poly is in front of old_poly plane */
+                   elist[old_edge].style = p->frontfacing ? above : below;
+               }       else {
+                   elist[old_edge].style =
+                       plist[old_poly].frontfacing ? above : below;
+               }
+           }
+           break;
+       case 3:
+           /* both frontfacing: nothing to do */
+           break;
+       } /* switch */
+    } else {
+       /* Ooops? build_networks() must have guessed incorrectly that
+        * this polygon should exist. */
+       return;
+    }
+}
+
+
+/* This somewhat monstrous routine fills the vlist, elist and plist
+ * dynamic arrays with values from all those plots. It strives to
+ * respect all the topological linkage between vertices, edges and
+ * polygons. E.g., it has to find the correct color for each edge,
+ * based on the orientation of the two polygons sharing it, WRT both
+ * the observer and each other. */
+/* NEW FEATURE HBB 20000715: allow non-grid datasets too, by storing
+ * only vertices and 'direct' edges, but no polygons or 'cross' edges
+ * */
+static void
+build_networks(struct surface_points *plots, int pcount)
+{
+    long int i;
+    struct surface_points *this_plot;
+    int surface;               /* count the surfaces (i.e. sub-plots) */
+    long int crv, ncrvs;       /* count isolines */
+    long int nverts;           /* count vertices */
+    long int max_crvlen;       /* maximal length of isoline in any plot */
+    long int nv, ne, np;       /* local poly/edge/vertex counts */
+    long int *north_polygons;  /* stores polygons of isoline above */
+    long int *these_polygons;  /* same, being built for use by next turn */
+    long int *north_edges;     /* stores edges of polyline above */
+    long int *these_edges;     /* same, being built for use by next turn */
+    struct iso_curve *icrvs;
+    int above = -3, below;     /* line type for edges of front/back side*/
+    struct lp_style_type *lp;  /* pointer to line and point properties */
+
+    /* Count out the initial sizes needed for the polygon and vertex
+     * lists. */
+    nv = ne = np = 0;
+    max_crvlen = -1;
+
+    for (this_plot = plots, surface = 0;
+       surface < pcount;
+       this_plot = this_plot->next_sp, surface++) {
+       long int crvlen;
+       
+       /* Quietly skip empty plots */
+       if (this_plot->plot_type == NODATA)
+           continue;
+
+       crvlen = this_plot->iso_crvs->p_count;
+
+       /* Allow individual plots to opt out of hidden3d calculations */
+       if (this_plot->opt_out_of_hidden3d)
+           continue;
+
+       /* register maximal isocurve length. Only necessary for
+        * grid-topology plots that will create polygons, so I can do
+        * it here, already. */
+       if (crvlen > max_crvlen)
+           max_crvlen = crvlen;
+
+       /* count 'curves' (i.e. isolines) and vertices in this plot */
+       nverts = 0;
+       if(this_plot->plot_type == FUNC3D) {
+           ncrvs = 0;
+           for(icrvs = this_plot->iso_crvs;
+               icrvs; icrvs = icrvs->next) {
+               ncrvs++;
+           }
+           nverts += ncrvs * crvlen;
+       } else if(this_plot->plot_type == DATA3D) {
+           ncrvs = this_plot->num_iso_read;
+           if (this_plot->has_grid_topology)
+               nverts += ncrvs * crvlen;
+           else if (this_plot->plot_style == VECTOR)
+               nverts += this_plot->iso_crvs->p_count;
+           else {
+               /* have to check each isoline separately: */
+               for (icrvs = this_plot->iso_crvs; icrvs; icrvs = icrvs->next)
+                   nverts += icrvs->p_count;
+           }
+       } else {
+           graph_error("Plot type is neither function nor data");
+           return;
+       }
+
+       /* To avoid possibly suprising error messages, several 2d-only
+        * plot styles are mapped to others, that are genuinely
+        * available in 3d. */
+       switch (this_plot->plot_style) {
+       case LINESPOINTS:
+       case STEPS:
+       case FSTEPS:
+       case HISTEPS:
+       case LINES:
+           nv += nverts;
+           ne += nverts - ncrvs;
+           if (this_plot->has_grid_topology) {
+               ne += 2 * nverts - ncrvs - 2 * crvlen + 1;
+               np += 2 * (ncrvs - 1) * (crvlen - 1);
+           }
+           break;
+       case BOXES:
+       case FILLEDCURVES:
+       case IMPULSES:
+       case VECTOR:
+           nv += 2 * nverts;
+           ne += nverts;
+           break;
+       case POINTSTYLE:
+       default:
+           /* treat all remaining ones like 'points' */
+           nv += nverts;
+           ne += nverts; /* a 'phantom edge' per isolated point */
+           break;
+       } /* switch */
+    } /* for (plots) */
+
+    /* Check for no data at all */
+    if (max_crvlen <= 0)
+       return;
+
+    /* allocate all the lists to the size we need: */
+    resize_dynarray(&vertices, nv);
+    resize_dynarray(&edges, ne);
+    resize_dynarray(&polygons, np);
+
+    /* allocate the storage for polygons and edges of the isoline just
+     * above the current one, to allow easy access to them from the
+     * current isoline */
+    north_polygons = gp_alloc(2 * max_crvlen * sizeof (long int),
+                             "hidden north_polys");
+    these_polygons = gp_alloc(2 * max_crvlen * sizeof (long int),
+                             "hidden these_polys");
+    north_edges = gp_alloc(3 * max_crvlen * sizeof (long int),
+                          "hidden north_edges");
+    these_edges = gp_alloc(3 * max_crvlen * sizeof (long int),
+                          "hidden these_edges");
+
+    /* initialize the lists, all in one large loop. This is different
+     * from the previous approach, which went over the vertices,
+     * first, and only then, in new loop, built polygons */
+    for (this_plot = plots, surface = 0;
+        surface < pcount;
+        this_plot = this_plot->next_sp, surface++) {
+       lp_style_type *lp_style = NULL;
+       TBOOLEAN color_from_column = this_plot->pm3d_color_from_column;
+       long int crvlen;
+
+       /* Quietly skip empty plots */
+       if (this_plot->plot_type == NODATA)
+           continue;
+
+       crvlen = this_plot->iso_crvs->p_count;
+
+       /* Allow individual plots to opt out of hidden3d calculations */
+       if (this_plot->opt_out_of_hidden3d)
+           continue;
+
+       lp = &(this_plot->lp_properties);
+       above = this_plot->lp_properties.l_type;
+       below = this_plot->lp_properties.l_type + hiddenBacksideLinetypeOffset;
+
+       /* calculate the point symbol type: */
+       /* Assumest hat upstream functions have made sure this is
+        * initialized sensibly --- thou hast been warned */
+       lp_style = &(this_plot->lp_properties);
+
+       /* HBB 20000715: new initialization code block for non-grid
+        * structured datasets. Sufficiently different from the rest
+        * to warrant separate code, I think. */
+       if (! this_plot->has_grid_topology) {
+           for (crv = 0, icrvs = this_plot->iso_crvs;
+                icrvs;
+                crv++, icrvs = icrvs->next) {
+               struct coordinate GPHUGE *points = icrvs->points;
+               long int previousvertex = -1;
+
+#ifdef EAM_DATASTRINGS
+               /* To handle labels we must look inside a separate list */
+               /* rather than just walking through the points arrays.  */
+               if (this_plot->plot_style == LABELPOINTS) {
+                   struct text_label *label;
+                   long int thisvertex;
+                   struct coordinate labelpoint = {0};
+
+                   lp->pointflag = 1; /* Labels can use the code for hidden points */
+                   for (label = this_plot->labels->next; label != NULL; label = label->next) {
+                       labelpoint.x = label->place.x;
+                       labelpoint.y = label->place.y;
+                       labelpoint.z = label->place.z;
+                       thisvertex = store_vertex(&labelpoint, 
+                               &(this_plot->lp_properties), color_from_column);
+                       if (thisvertex < 0)
+                           continue;
+                       (vlist+thisvertex)->label = label;
+                       store_edge(thisvertex, edir_point, crvlen, lp, above);
+                   }
+               } else
+#endif
+
+               for (i = 0; i < icrvs->p_count; i++) {
+                   long int thisvertex, basevertex;
+
+                   thisvertex = store_vertex(points + i, lp_style,
+                                             color_from_column);
+
+                   if (this_plot->plot_style == VECTOR) {
+                       store_vertex(icrvs->next->points+i, 0, 0);
+                   }
+
+                   if (thisvertex < 0) {
+                       previousvertex = thisvertex;
+                       continue;
+                   }
+
+                   switch (this_plot->plot_style) {
+                   case LINESPOINTS:
+                   case STEPS:
+                   case FSTEPS:
+                   case HISTEPS:
+                   case LINES:
+                       if (previousvertex >= 0)
+                           store_edge(thisvertex, edir_west, 0, lp, above);
+                       break;
+                   case VECTOR:
+                       store_edge(thisvertex, edir_vector, 0, lp, above);
+                       break;
+                   case BOXES:
+                   case FILLEDCURVES:
+                   case IMPULSES:
+                       /* set second vertex to the low end of zrange */
+                       {
+                           coordval remember_z = points[i].z;
+
+                           points[i].z = axis_array[FIRST_Z_AXIS].min;
+                           basevertex = store_vertex(points + i, lp_style,
+                                                     color_from_column);
+                           points[i].z = remember_z;
+                       }
+                       if (basevertex > 0)
+                           store_edge(basevertex, edir_impulse, 0, lp, above);
+                       break;
+
+                   case POINTSTYLE:
+                   default:    /* treat all the others like 'points' */
+                       store_edge(thisvertex, edir_point, crvlen, lp, above);
+                       break;
+                   } /* switch(plot_style) */
+
+                   previousvertex = thisvertex;
+               } /* for(vertex) */
+           } /* for(crv) */
+
+           continue;           /* done with this plot! */
+       }
+
+       /* initialize stored indices of north-of-this-isoline polygons and
+        * edges properly */
+       for (i=0; i < this_plot->iso_crvs->p_count; i++) {
+           north_polygons[2 * i]
+               = north_polygons[2 * i + 1]
+               = north_edges[3 * i]
+               = north_edges[3 * i + 1]
+               = north_edges[3 * i + 2]
+               = -3;
+       }
+
+       for (crv = 0, icrvs = this_plot->iso_crvs;
+            icrvs;
+            crv++, icrvs = icrvs->next) {
+           struct coordinate GPHUGE *points = icrvs->points;
+
+           for (i = 0; i < icrvs->p_count; i++) {
+               long int thisvertex, basevertex;
+               long int e1, e2, e3;
+               long int pnum;
+
+               thisvertex = store_vertex(points + i, lp_style,
+                                         color_from_column);
+
+               /* Preset the pointers to the polygons and edges
+                * belonging to this isoline */
+               these_polygons[2 * i] = these_polygons[2 * i + 1]
+                   = these_edges[3 * i] = these_edges[3 * i + 1]
+                   = these_edges[3 * i + 2]
+                   = -3;
+
+               switch (this_plot->plot_style) {
+               case LINESPOINTS:
+               case STEPS:
+               case FSTEPS:
+               case HISTEPS:
+               case LINES:
+                   if (i > 0) {
+                       /* not first point, so we might want to set up
+                        * the edge(s) to the left of this vertex */
+                       if (thisvertex < 0) {
+                           if ((crv > 0)
+                               && (hiddenShowAlternativeDiagonal)
+                               ) {
+                               /* this vertex is invalid, but the
+                                * other three might still form a
+                                * valid triangle, facing northwest to
+                                * do that, we'll need the 'wrong'
+                                * diagonal, which goes from SW to NE:
+                                * */
+                               these_edges[i*3+2] = e3
+                                   = store_edge(vertices.end - 1, edir_NE, crvlen,
+                                                lp, above);
+                               if (e3 > -2) {
+                                   /* don't store this polygon for
+                                    * later: it doesn't share edges
+                                    * with any others to the south or
+                                    * east, so there's need to */
+                                   pnum
+                                       = store_polygon(vertices.end - 1, pdir_NW, crvlen);
+                                   /* The other two edges of this
+                                    * polygon need to be checked
+                                    * against the neighboring
+                                    * polygons' orientations, before
+                                    * being coloured */
+                                   color_edges(e3, these_edges[3*(i-1) +1],
+                                               pnum, these_polygons[2*(i-1) + 1],
+                                               above, below);
+                                   color_edges(e3, north_edges[3*i],
+                                               pnum, north_polygons[2*i], above, below);
+                               }
+                           }
+                           break; /* nothing else to do for invalid vertex */
+                       }
+
+                       /* Coming here means that the current vertex
+                        * is valid: check the other three of this
+                        * cell, by trying to set up the edges from
+                        * this one to there */
+                       these_edges[i*3] = e1
+                           = store_edge(thisvertex, edir_west, crvlen, lp, above);
+
+                       if (crv > 0) { /* vertices to the north exist */
+                           these_edges[i*3 + 1] = e2
+                               = store_edge(thisvertex, edir_north, crvlen, lp, above);
+                           these_edges[i*3 + 2] = e3
+                               = store_edge(thisvertex, edir_NW, crvlen, lp, above);
+                           if (e3 > -2) {
+                               /* diagonal edge of this cell is OK,
+                                * so try to build both the polygons:
+                                * */
+                               if (e1 > -2) {
+                                   /* one pair of edges is valid: put
+                                    * first polygon, which points
+                                    * towards the southwest */
+                                   these_polygons[2*i] = pnum
+                                       = store_polygon(thisvertex, pdir_SW, crvlen);
+                                   color_edges(e1, these_edges[3*(i-1)+1],
+                                               pnum, these_polygons[2*(i-1)+ 1], above, below );
+                               }
+                               if (e2 > -2) {
+                                   /* other pair of two is fine, put
+                                    * the northeast polygon: */
+                                   these_polygons[2*i + 1] = pnum
+                                       = store_polygon(thisvertex, pdir_NE, crvlen);
+                                   color_edges(e2, north_edges[3*i],
+                                               pnum, north_polygons[2*i], above, below);
+                               }
+                               /* In case these two new polygons
+                                * differ in orientation, find good
+                                * coloring of the diagonal */
+                               color_edges(e3, e3, these_polygons[2*i],
+                                           these_polygons[2*i+1], above, below);
+                           } /* if e3 valid */
+                           else if ((e1 > -2) && (e2 > -2)
+                                    && hiddenShowAlternativeDiagonal) {
+                               /* looks like all but the north-west
+                                * vertex are usable, so we set up the
+                                * southeast-pointing triangle, using
+                                * the 'wrong' diagonal: */
+                               these_edges[3*i + 2] = e3
+                                   = store_edge(thisvertex, edir_NE, crvlen, lp, above);
+                               if (e3 > -2) {
+                                   /* fill this polygon into *both*
+                                    * polygon places for this
+                                    * quadrangle, as this triangle
+                                    * coincides with both edges that
+                                    * will be used by later polygons
+                                    * */
+                                   these_polygons[2*i] = these_polygons[2*i+1] = pnum
+                                       = store_polygon(thisvertex, pdir_SE, crvlen);
+                                   /* This case is somewhat special:
+                                    * all edges are new, so there is
+                                    * no other polygon orientation to
+                                    * consider */
+                                   if (!plist[pnum].frontfacing)
+                                       elist[e1].style = elist[e2].style = elist[e3].style
+                                           = below;
+                               }
+                           }
+                       }
+                   } else if ((crv > 0)
+                              && (thisvertex >= 0)) {
+                       /* We're at the west border of the grid, but
+                        * not on the north one: put vertical end-wall
+                        * edge:*/
+                       these_edges[3*i + 1] =
+                           store_edge(thisvertex, edir_north, crvlen, lp, above);
+                   }
+                   break;
+
+               case BOXES:
+               case FILLEDCURVES:
+               case IMPULSES:
+                   if (thisvertex < 0)
+                       break;
+
+                   /* set second vertex to the low end of zrange */
+                   {
+                       coordval remember_z = points[i].z;
+
+                       points[i].z = axis_array[FIRST_Z_AXIS].min;
+                       basevertex = store_vertex(points + i, lp_style,
+                                                 color_from_column);
+                       points[i].z = remember_z;
+                   }
+                   if (basevertex > 0)
+                       store_edge(basevertex, edir_impulse, 0, lp, above);
+                   break;
+
+               case POINTSTYLE:
+               default:        /* treat all the others like 'points' */
+                   if (thisvertex < 0) /* Ignore invalid vertex */
+                       break;
+                   store_edge(thisvertex, edir_point, crvlen, lp, above);
+                   break;
+               } /* switch */
+           } /* for(i) */
+
+           /* Swap the 'north' lists of polygons and edges with
+            * 'these' ones, which have been filled in the pass
+            * through this isocurve */
+           {
+               long int *temp = north_polygons;
+               north_polygons = these_polygons;
+               these_polygons = temp;
+
+               temp = north_edges;
+               north_edges = these_edges;
+               these_edges = temp;
+           }
+       } /* for(isocrv) */
+    } /* for(plot) */
+
+    free (these_polygons);
+    free (north_polygons);
+    free (these_edges);
+    free (north_edges);
+}
+
+/* Sort the elist in order of growing zmax. Uses qsort on an array of
+ * plist indices, and then fills in the 'next' fields in struct
+ * polygon to store the resulting order inside the plist */
+/* HBB 20010720: removed 'static' to avoid HP-sUX gcc bug */
+int
+compare_edges_by_zmin(SORTFUNC_ARGS p1, SORTFUNC_ARGS p2)
+{
+    return SIGN(vlist[elist[*(const long *) p1].v2].z
+               - vlist[elist[*(const long *) p2].v2].z);
+}
+
+static void
+sort_edges_by_z()
+{
+    long *sortarray, i;
+    p_edge this;
+
+    if (!edges.end)
+       return;
+
+    sortarray = gp_alloc((unsigned long) sizeof(long) * edges.end,
+                        "hidden sort edges");
+    /* initialize sortarray with an identity mapping */
+    for (i = 0; i < edges.end; i++)
+       sortarray[i] = i;
+    /* sort it */
+    qsort(sortarray, (size_t) edges.end, sizeof(long), compare_edges_by_zmin);
+
+    /* traverse plist in the order given by sortarray, and set the
+     * 'next' pointers */
+    this = elist + sortarray[0];
+    for (i = 1; i < edges.end; i++) {
+       this->next = sortarray[i];
+       this = elist + sortarray[i];
+    }
+    this->next = -1L;
+
+    /* 'efirst' is the index of the leading element of plist */
+    efirst = sortarray[0];
+
+    free(sortarray);
+}
+
+/* HBB 20010720: removed 'static' to avoid HP-sUX gcc bug */
+int
+compare_polys_by_zmax(SORTFUNC_ARGS p1, SORTFUNC_ARGS p2)
+{
+    return (SIGN(plist[*(const long *) p1].zmax
+                - plist[*(const long *) p2].zmax));
+}
+
+static void
+sort_polys_by_z()
+{
+    long *sortarray, i;
+    p_polygon this;
+
+    if (!polygons.end)
+       return;
+
+    sortarray = gp_alloc((unsigned long) sizeof(long) * polygons.end,
+                        "hidden sortarray");
+
+    /* initialize sortarray with an identity mapping */
+    for (i = 0; i < polygons.end; i++)
+       sortarray[i] = i;
+
+    /* sort it */
+    qsort(sortarray, (size_t) polygons.end, sizeof(long),
+         compare_polys_by_zmax);
+
+    /* traverse plist in the order given by sortarray, and set the
+     * 'next' pointers */
+#if HIDDEN3D_QUADTREE
+    /* HBB 20000716: Loop backwards, to ease construction of
+     * linked lists from the head: */
+    {
+       int grid_x, grid_y;
+       int grid_x_low, grid_x_high, grid_y_low, grid_y_high;
+
+       for (grid_x = 0; grid_x < QUADTREE_GRANULARITY; grid_x++)
+           for (grid_y = 0; grid_y < QUADTREE_GRANULARITY; grid_y++)
+               quadtree[grid_x][grid_y] = -1;
+
+       for (i=polygons.end - 1; i >= 0; i--) {
+           this = plist + sortarray[i];
+
+           grid_x_low = coord_to_treecell(this->xmin);
+           grid_x_high = coord_to_treecell(this->xmax);
+           grid_y_low = coord_to_treecell(this->ymin);
+           grid_y_high = coord_to_treecell(this->ymax);
+
+           for (grid_x = grid_x_low; grid_x <= grid_x_high; grid_x++) {
+               for (grid_y = grid_y_low; grid_y <= grid_y_high; grid_y++) {
+                   p_qtreelist newhead = nextfrom_dynarray(&qtree);
+
+                   newhead->next = quadtree[grid_x][grid_y];
+                   newhead->p = sortarray[i];
+
+                   quadtree[grid_x][grid_y] = newhead - qlist;
+               }
+           }
+       }
+    }
+
+#else /* HIDDEN3D_QUADTREE */
+    this = plist + sortarray[0];
+    for (i = 1; i < polygons.end; i++) {
+       this->next = sortarray[i];
+       this = plist + sortarray[i];
+    }
+    this->next = -1L;
+    /* 'pfirst' is the index of the leading element of plist */
+#endif /* HIDDEN3D_QUADTREE */
+    pfirst = sortarray[0];
+
+    free(sortarray);
+}
+
+
+#define LASTBITS (USHRT_BITS -1)       /* ????? */
+
+/************************************************/
+/*******            Drawing the polygons ********/
+/************************************************/
+
+/* draw a single vertex as a point symbol, if requested by the chosen
+ * plot style (linespoints, points, or dots...) */
+static void
+draw_vertex(p_vertex v)
+{
+    unsigned int x, y;
+
+    TERMCOORD(v, x, y);
+    if (v->lp_style && v->lp_style->p_type >= 0 && !clip_point(x,y)) {
+       int colortype = v->lp_style->pm3d_color.type;
+
+#ifdef EAM_DATASTRINGS
+       if (v->label)  {
+           write_label(x,y, v->label);
+           v->lp_style = NULL;
+           return;
+       }
+#endif
+
+       /* EAM DEBUG - Check for extra point properties */
+       if (colortype == TC_LT)
+           /* Should have been set already! */
+           ;
+       else if (colortype == TC_RGB && v->lp_style->pm3d_color.lt == LT_COLORFROMCOLUMN)
+           set_rgbcolor(v->real_z);
+       else if (colortype == TC_RGB)
+           set_rgbcolor(v->lp_style->pm3d_color.lt);
+       else if (colortype == TC_Z)
+           set_color( cb2gray(z2cb(v->real_z)) );
+
+#ifdef HIDDEN3D_VAR_PTSIZE
+       if (v->lp_style->p_size == PTSZ_VARIABLE)
+           (term->pointsize)(pointsize * v->original->CRD_PTSIZE);
+#endif
+
+       (term->point)(x,y, v->lp_style->p_type);
+
+       /* vertex has been drawn --> flag it as done */
+       v->lp_style = NULL;
+    }
+}
+
+
+/* The function that actually does the drawing of the visible portions
+ * of lines */
+/* HBB 20001108: changed to take the pointers to the end vertices as
+ * additional arguments. */
+static void
+draw_edge(p_edge e, p_vertex v1, p_vertex v2)
+{
+    assert (e >= elist && e < elist + edges.end);
+
+    draw3d_line_unconditional(v1, v2, e->lp, e->style);
+    if (e->lp->pointflag) {
+       draw_vertex(v1);
+       draw_vertex(v2);
+    }
+}
+
+
+/*************************************************************/
+/*************************************************************/
+/*******   The depth sort algorithm (in_front) and its  ******/
+/*******   whole lot of helper functions                ******/
+/*************************************************************/
+/*************************************************************/
+
+/* Split a given line segment into two at an inner point. The inner
+ * point is specified as a fraction of the line-length (0 is V1, 1 is
+ * V2) */
+/* HBB 20001108: changed to now take two vertex pointers as its
+ * arguments, rather than an edge pointer. */
+/* HBB 20001204: changed interface again. Now use vertex indices,
+ * rather than pointers, to avoid problems with dangling pointers
+ * after nextfrom_dynarray() call. */
+static long
+split_line_at_ratio(
+    long vnum1, long vnum2,    /* vertex indices of line to split */
+    double w)                  /* where to split it */
+{
+    p_vertex v;
+
+    if (EQ(w, 0.0))
+       return vnum1;
+    if (EQ(w, 1.0))
+       return vnum2;
+
+    /* Create a new vertex */
+    v = nextfrom_dynarray(&vertices);
+
+    v->x = (vlist[vnum2].x - vlist[vnum1].x) * w + vlist[vnum1].x;
+    v->y = (vlist[vnum2].y - vlist[vnum1].y) * w + vlist[vnum1].y;
+    v->z = (vlist[vnum2].z - vlist[vnum1].z) * w + vlist[vnum1].z;
+    v->real_z = (vlist[vnum2].real_z - vlist[vnum1].real_z) * w
+       + vlist[vnum1].real_z;
+
+    /* no point symbol for vertices generated by splitting an edge */
+    v->lp_style = NULL;
+
+    /* additional checks to prevent adding unnecessary vertices */
+    if (V_EQUAL(v, vlist + vnum1)) {
+       droplast_dynarray(&vertices);
+       return vnum1;
+    }
+    if (V_EQUAL(v, vlist + vnum2)) {
+       droplast_dynarray(&vertices);
+       return vnum2;
+    }
+
+    return (v - vlist);
+}
+
+
+/* Compute the 'signed area' of 3 points in their 2d projection
+ * to the x-y plane. Essentially the z component of the crossproduct.
+ * Should come out positive if v1, v2, v3 are ordered counter-clockwise */
+
+static GP_INLINE double
+area2D(p_vertex v1, p_vertex v2, p_vertex v3)
+{
+    double
+       dx12 = v2->x - v1->x,   /* x/y components of (v2-v1) and (v3-v1) */
+       dx13 = v3->x - v1->x,
+       dy12 = v2->y - v1->y,
+       dy13 = v3->y - v1->y;
+    return (dx12 * dy13 - dy12 * dx13);
+}
+
+/* Utility routine: takes an edge and makes a new one, which is a fragment
+ * of the old one. The fragment inherits the line style and stuff of the
+ * given edge; only the two new vertices are different. The new edge
+ * is then passed to in_front, for recursive handling */
+/* HBB 20001108: Changed from edge pointer to edge index. Don't
+ * allocate a fresh anymore, as this is no longer needed after the
+ * change to in_front().  What remains of this function may no longer
+ * be worth having. I.e. it can be replaced by a direct recursion call
+ * of in_front(), sometime soon. */
+static GP_INLINE void
+handle_edge_fragment(long edgenum, long vnum1, long vnum2, long firstpoly)
+{
+#if !HIDDEN3D_QUADTREE
+    /* Avoid checking against the same polygon again. */
+    firstpoly = plist[firstpoly].next;
+#endif
+    in_front(edgenum, vnum1, vnum2, &firstpoly);
+}
+
+/*********************************************************************/
+/* The actual heart of all this: determines if edge at index 'edgenum'
+ * of the elist is in_front of all the polygons, or not. If necessary,
+ * it will recursively call itself to isolate more than one visible
+ * fragment of the input edge. Wherever possible, recursion is
+ * avoided, by in-place modification of the edge.
+ *
+ * The visible fragments are then drawn by a call to 'draw_edge' from
+ * inside this routine. */
+/*********************************************************************/
+/* HBB 20001108: changed to now take the vertex numbers as additional
+ * arguments. The idea is to not overwrite the endpoint stored with
+ * the edge, so Test 2 will catch on even after the subject edge has
+ * been split up before one of its two polygons is tested against
+ * it. FIXME: allocates new vertices when splitting, but never frees
+ * them, currently. */
+
+static int
+in_front(
+    long edgenum,              /* number of the edge in elist */
+    long vnum1, long vnum2,    /* numbers of its endpoints */
+    long *firstpoly)           /* first plist index to consider */
+{
+    p_polygon p;               /* pointer to current testing polygon */
+    long int polynum;          /* ... and its index in the plist */
+    p_vertex v1, v2;           /* pointers to vertices of input edge */
+
+    coordval xmin, xmax;       /* all of these are for the edge */
+    coordval ymin, ymax;
+    coordval zmin, zmax;
+#if HIDDEN3D_GRIDBOX
+    unsigned int xextent;      /* extent bitmask in x direction */
+    unsigned int yextent;      /* same, in y direction */
+
+# define SET_XEXTENT \
+  xextent = CALC_BITRANGE(xmin, xmax);
+# define SET_YEXTENT \
+  yextent = CALC_BITRANGE(ymin, ymax);
+#else
+# define SET_XEXTENT /* nothing */
+# define SET_YEXTENT /* nothing */
+#endif
+#if HIDDEN3D_QUADTREE
+    int grid_x, grid_y;
+    int grid_x_low, grid_x_high;
+    int grid_y_low, grid_y_high;
+    long listhead;
+#endif
+
+    /* zmin of the edge, as it started out. This is needed separately to
+     * allow modifying '*firstpoly', without moving it too far to the
+     * front. */
+    coordval first_zmin;
+
+    /* macro for eliminating tail-recursion inside in_front: when the
+     * current edge is modified, recompute all function-wide status
+     * variables. Note that it guarantees that v1 is always closer to
+     * the viewer than v2 (in z direction) */
+    /* HBB 20001108: slightly changed so it can be called with vnum1
+     * and vnum2 as its arguments, too */
+#define setup_edge(vert1, vert2)               \
+    do {                                       \
+       if (vlist[vert1].z > vlist[vert2].z) {  \
+           v1 = vlist + (vert1);               \
+           v2 = vlist + (vert2);               \
+       } else {                                \
+           v1 = vlist + (vert2);               \
+           v2 = vlist + (vert1);               \
+       }                                       \
+       vnum1 = v1 - vlist;                     \
+       vnum2 = v2 - vlist;                     \
+       zmax = v1->z;   zmin = v2->z;           \
+                                               \
+       if (v1->x > v2->x) {                    \
+           xmin = v2->x;       xmax = v1->x;   \
+       } else {                                \
+           xmin = v1->x;       xmax = v2->x;   \
+       }                                       \
+       SET_XEXTENT;                            \
+                                               \
+       if (v1->y > v2->y) {                    \
+           ymin = v2->y;       ymax = v1->y;   \
+       } else {                                \
+           ymin = v1->y;       ymax = v2->y;   \
+       }                                       \
+       SET_YEXTENT;                            \
+    } while (0) /* end macro setup_edge */
+
+    /* use the macro for initial setup, too: */
+    setup_edge(vnum1, vnum2);
+
+    first_zmin = zmin;
+
+#if HIDDEN3D_QUADTREE
+    grid_x_low = coord_to_treecell(xmin);
+    grid_x_high = coord_to_treecell(xmax);
+    grid_y_low = coord_to_treecell(ymin);
+    grid_y_high = coord_to_treecell(ymax);
+
+    for (grid_x = grid_x_low; grid_x <= grid_x_high; grid_x ++)
+       for (grid_y = grid_y_low; grid_y <= grid_y_high; grid_y ++)
+           for (listhead = quadtree[grid_x][grid_y];
+                listhead >= 0;
+                listhead = qlist[listhead].next)
+#else /* HIDDEN3D_QUADTREE */
+    /* loop over all the polygons in the sorted list, starting at the
+     * currently first (i.e. furthest, from the viewer) polgon. */
+    for (polynum = *firstpoly; polynum >=0; polynum = p->next)
+#endif /* HIDDEN3D_QUADTREE */
+       {
+           /* shortcut variables for the three vertices of 'p':*/
+           p_vertex w1, w2, w3;
+           /* signed areas of each of e's vertices wrt. the edges of
+            * p. I store them explicitly, because they are used more
+            * than once, eventually */
+           double e_side[2][POLY_NVERT];
+           /* signed areas of each of p's vertices wrt. to edge e. Stored
+            * for re-use in intersection calculations, as well */
+           double p_side[POLY_NVERT];
+           /* values got from throwing the edge vertices into the plane
+            * equation of the polygon. Used for testing if the vertices are
+            * in front of or behind the plane, and also to determine the
+            * point where the edge goes through the polygon, by
+            * interpolation. */
+           double v1_rel_pplane, v2_rel_pplane;
+           /* Orientation of polygon wrt. to the eye: front or back side
+            * visible? */
+           coordval polyfacing;
+           int whichside;
+           /* stores classification of cases as 4 2-bit patterns, mainly */
+           unsigned int classification[POLY_NVERT + 1];
+
+#if HIDDEN3D_QUADTREE
+           polynum = qlist[listhead].p;
+#endif
+           p = plist + polynum;
+
+           /* OK, off we go with the real work. This algo is mainly
+            * following the one of 'HLines.java', as described in the
+            * book 'Computer Graphics for Java Programmers', by Dutch
+            * professor Leen Ammeraal, published by J.Wiley & Sons,
+            * ISBN 0 471 98142 7. It happens to the only(!) actual
+            * code or algorithm example I got out of a question to
+            * the Newsgroup comp.graphics.algorithms, asking for a
+            * hidden *line* removal algorithm. */
+
+           /* Test 1 (2D): minimax tests. Do x/y ranges of polygon
+            * and edge have any overlap? */
+           if (0
+#if HIDDEN3D_GRIDBOX
+               /* First, check by comparing the extent bit patterns: */
+               || (!(xextent & p->xbits))
+               || (!(yextent & p->ybits))
+#endif
+               || (p->xmax < xmin)
+               || (p->xmin > xmax)
+               || (p->ymax < ymin)
+               || (p->ymin > ymax)
+               )
+               continue;
+
+           /* Tests 2 and 3 switched... */
+
+           /* Test 3 (3D): Is edge completely in front of polygon? */
+           if (p->zmax < zmin) {
+               /* Polygon completely behind this edge. Move start of
+                * relevant plist to this point, to speed up next
+                * run. This makes use of the fact that elist is also
+                * kept in upwardly sorted order of zmin, i.e. the
+                * condition found here will also hold for all coming
+                * edges in the list */
+               if (p->zmax < first_zmin)
+                   *firstpoly = polynum;
+               continue;       /* this polygon is done with */
+           }
+
+           /* Test 2 (0D): does edge belong to this very polygon? */
+           /* 20001108: to make this rejector more effective, do keep
+            * the original edge vertices unchanged */
+           if (1
+               && (0
+                   || (p->vertex[0] == elist[edgenum].v1)
+                   || (p->vertex[1] == elist[edgenum].v1)
+                   || (p->vertex[2] == elist[edgenum].v1)
+                   )
+               && (0
+                   || (p->vertex[0] == elist[edgenum].v2)
+                   || (p->vertex[1] == elist[edgenum].v2)
+                   || (p->vertex[2] == elist[edgenum].v2)
+                   )
+               )
+               continue;
+
+           w1 = vlist + p->vertex[0];
+           w2 = vlist + p->vertex[1];
+           w3 = vlist + p->vertex[2];
+
+
+           /* Test 4 (2D): Is edge fully on the 'wrong side' of one of the
+            * polygon edges?
+            *
+            * Done by comparing signed areas (cross-product z components of
+            * both edge endpoints) to that of the polygon itself (as given by
+            * the plane normal z component). If both v1 and v2 are found on
+            * the 'outside' side of a polygon edge (interpreting it as
+            * infinite line), the polygon can't obscure this edge. All the
+            * signed areas are remembered, for later tests. */
+           polyfacing = p->plane[2];
+           whichside = SIGN(polyfacing);
+           if (!whichside)
+               /* We're looking at the border of this polygon. It
+                * looks like an infitisemally thin line, i.e. it'll
+                * be practically invisible*/
+               /* FIXME: should such a polygon have been stored in the plist,
+                * in the first place??? */
+               continue;
+
+           /* p->plane[2] is now forced >=0, so it doesn't tell anything
+            * about which side the polygon is facing */
+           whichside = (p->frontfacing) ? -1 : 1;
+
+#if 0
+           /* Just make sure I got this the right way round: */
+           if (p->frontfacing)
+               assert (GE(area2D(w1, w2, w3),0));
+           else
+               assert (GE(0,area2D(w1, w2, w3)));
+#endif
+
+           /* classify both endpoints of the test edge, by their
+            * position relative to some plane that defines the volume
+            * shadowed by a triangle. Used both by macro 'checkside',
+            * and for individual checking, below --> put this into a
+            * macro */
+#define classify(par1, par2, classnum)                                 \
+           do {                                                        \
+               if (GR(par1, 0)) {                                      \
+                   /* v1 strictly outside --> v2 in-plane is enough,   \
+                       already : */                                    \
+                   classification[classnum]                            \
+                       = (1 << 0) | ((GE(par2, 0)) << 1);              \
+               } else if (GR(par2, 0)) {                               \
+                   /* v2 strictly outside --> v1 in-plane is enough,   \
+                       already : */                                    \
+                   classification[classnum]                            \
+                       = ((GE(par1, 0)) << 0) | (1 << 1);              \
+               } else {                                                \
+                   /* as neither one of them is strictly outside,      \
+                    * there's no need for any edge-splitting, at all.  \
+                    * */                                               \
+                   classification[classnum] = 0;                       \
+               }                                                       \
+           } while (0)
+
+           /* code block used thrice, so put into a macro. The
+            * 'area2D' calls return <0, ==0 or >0 if the three
+            * vertices passed to it are in clockwise order, colinear,
+            * or in conter-clockwise order, respectively. So the sign
+            * of this can be used to determine if a point is inside
+            * or outside the shadow volume. */
+#define checkside(a,b,s)                                       \
+           e_side[0][s] = whichside * area2D((a), (b), v1);    \
+           e_side[1][s] = whichside * area2D((a), (b), v2);    \
+           classify(e_side[0][s], e_side[1][s], s);            \
+           if (classification[s] == 3)                         \
+               continue;
+
+           checkside(w1, w2, 0);
+           checkside(w2, w3, 1);
+           checkside(w3, w1, 2);
+#undef checkside               /* get rid of that macro, again */
+
+
+           /* Test 5 (2D): Does the whole polygon lie on one and the same
+            * side of the tested edge? Again, area2D is used to determine on
+            * which side of a given edge a vertex lies. */
+           /* HBB 20000621: this test only makes sense if this is a
+            * 'real' edge, not just a single point */
+           /* HBB 20051206: but p_side[] may still have to be computed, to be used
+            * by Test 9 */
+           p_side[0] = area2D(v1, v2, w1);
+           p_side[1] = area2D(v1, v2, w2);
+           p_side[2] = area2D(v1, v2, w3);
+           if (v1 != v2) {
+               /* HBB 20001104: made this more restrictive: only
+                * reject p if areas are greater than 0, i.e. don't
+                * allow EQ(..,0). Otherwise, edges coincident with a
+                * polygon edge in 2D will not be hidden by it. */
+               /* FIXME HBB 20001108: some more code will be needed
+                * here or further down below. It will currently fail
+                * if two edges fall exactly on top of each other
+                * (like the crossover line in the Klein-bottle demo):
+                * both edges will be hidden by the other's polygon,
+                * essentially */
+#if 0
+               if (0
+                   || (GR(p_side[0], 0)
+                       && GR(p_side[1], 0)
+                       && GR(p_side[2], 0)
+                       )
+                   || (GR(0 , p_side[0])
+                       && GR(0 , p_side[1])
+                       && GR(0 , p_side[2])
+                       )
+                   )
+                   continue;
+#else
+               /* HBB 20020406: try to repair this */
+               if (0
+                   || (GE(p_side[0], 0)
+                       && GE(p_side[1], 0)
+                       && GE(p_side[2], 0)
+                       )
+                   || (GE(0 , p_side[0])
+                       && GE(0 , p_side[1])
+                       && GE(0 , p_side[2])
+                       )
+                   )
+                   continue;
+#endif
+           }
+
+           /* Test 6 (3D): does the whole edge lie on the viewer's
+            * side of the polygon's plane? */
+           v1_rel_pplane = eval_plane_equation(p->plane, v1);
+           v2_rel_pplane = eval_plane_equation(p->plane, v2);
+
+           /* Condense into classification bit pattern for v1 and v2
+            * wrt.  this plane, and store into entry 'POLY_NVERT' of
+            * the classification array, following the three edges of
+            * the polygon. */
+           classify(v1_rel_pplane, v2_rel_pplane, POLY_NVERT);
+
+           if (classification[POLY_NVERT] == 3)
+               continue;                                                                       /* edge completely in front of polygon */
+
+           /* Test 7 (2D): is edge completely inside the triangle?
+            * The stored results from Test 4 make this rather easy to
+            * check: if a point is oriented equally wrt. all three
+            * edges, and also to the triangle itself, the point is
+            * inside the 2D triangle: */
+
+           /* search for any one bits (--> they mean 'outside') */
+           if (! (classification[0]
+                  | classification[1]
+                  | classification[2])) {
+
+               /* Edge is completely inside the polygon's 2D
+                * projection. Unlike Ammeraal, I have to check for
+                * edges penetrating polygons, as well. */
+
+               /* Check if edge really doesn't intersect the triangle
+                * (this happens in roughly one third of the
+                * cases). If both vertices have been classified as
+                * 'behind', the result is true even in the case of
+                * possibly intersecting polygons */
+               /* HBB 20020408: softened this check to allow edges
+                * not to be hidden by polygons they're fully inside.
+                * I.e. only declare the edge invisible if at least
+                * one of its endpoints is truly behind, not inside
+                * the polygon's plane. ---> FIXME 20001108 above */
+               if (!classification[POLY_NVERT]
+                   && (GR(0, v1_rel_pplane) || GR(0, v2_rel_pplane)))
+                   return 0;
+           }
+
+
+           /* Test 8 (3D): If no edge intersects any polygon, the edge must
+            * lie infront of the poly if the 'inside' vertex in front of it.
+            * This test doesn't work if lines might intersect polygons, so I
+            * took it out of my version of Ammeraal's algorithm. */
+
+
+           /* Test 9 (3D): The final 'catch-all' handler. We now know
+            * that the edges passes through the walls of the
+            * semifinite polygonal cylinder of invisible space
+            * stamped out by the polygon, somewhere, so we have to
+            * compute the intersection points with the 2D projection
+            * of the polygon and check whether they are infront of or
+            * behind the polygon, to isolate the visible fragments of
+            * the line. Method is to calc. the [0..1] parameter that
+            * describes the way from V1 to V2, for all intersections
+            * of the edge with any of the planes that define the
+            * 'shadow volume' of this polygon, and deciding what to
+            * do, with all this.
+            *
+            * The remaining work can be classified by the values of
+            * the variables calculated in previous tests:
+            * v{1/2}_rel_pplane, e_side[2][POLY_NVERT],
+            * p_side[POLY_NVERT]. Setting aside cases where one of
+            * them is zero, this leaves a total of 2^11 = 2048
+            * separate cases, in principal. Several of those have
+            * been sorted out, already, by the previous tests. That
+            * leaves us with 3*27 cases, essentially. The '3' cases
+            * differ by which of the three vertices of the polygon is
+            * alone on its side of the test edge.  That already fixes
+            * which two edges might intersect the test edge.  The 27
+            * cases differ by which of the edge points lies on which
+            * of the three planes in space (two sides, and the
+            * polygon plane) that remain interesting for the tests.
+            * That would normally make 2^2 = 4 cases per plane. But
+            * the 'both outside' cases for all three planes have
+            * already been taken care of, earlier, leaving 3 of those
+            * cases to be handled. This is the same for all three
+            * relevant planes, so we get 3^3 = 27 cases. */
+
+           {
+               /* the interception point parameters: */
+               double front_hit, this_hit, hit_in_poly;
+               long newvert[2];
+               int side1, side2;
+               double hit1, hit2;
+               unsigned int full_class;
+
+               /* find out which of the three edges would be intersected: */
+               if ((p_side[0] > 0) == (p_side[1] > 0)) {
+                   /* first two vertices on the same side, the third
+                    * is on the 'other' side of the test edge */
+                   side1 = 1;
+                   side2 = 2;
+               } else if ((p_side[0] > 0) == (p_side[2] > 0)) {
+                   /* vertex '1' on the other side */
+                   side1 = 0;
+                   side2 = 1;
+               } else {
+                   /* we now know that it must be the first vertex
+                    * that is on the 'other' side, as not all three
+                    * can be on different sides. */
+                   side1 = 0;
+                   side2 = 2;
+               }
+
+               /* Carry out the classification into those 27 cases,
+                * based upon classification bits precomputed above,
+                * and calculate the intersection parameters, as
+                * appropriate. Start by regarding the polygon's
+                * plane: '1' means point is in front of plane.
+                *
+                * First, some utility macros: */
+#define cleanup_hit(hit)                       \
+               do {                            \
+                   if (EQ(hit,0))              \
+                       hit=0;                  \
+                   else if (EQ(hit,1))         \
+                       hit=1;                  \
+               } while (0)
+
+#define hit_in_edge(hit) ((hit >= 0) && (hit <= 1))
+
+               /* find the intersection point through the front
+                * plane, if any: */
+               front_hit = 1e10;
+               if (classification[3]) {
+                   if (SIGN(v1_rel_pplane - v2_rel_pplane)) {
+                       front_hit = v1_rel_pplane / (v1_rel_pplane - v2_rel_pplane);
+                       cleanup_hit(front_hit);
+                   }
+                   if (!hit_in_edge(front_hit)) {
+                       front_hit = 1e10;
+                       classification[3] = 0;  /* not really intersecting */
+                   }
+               }
+
+
+               /* Find intersection points with the two relevant side
+                * planes of the shadow volume. A macro contains the
+                * code, to be used for both side1 and side2, to
+                * reduce code overhead: */
+#define check_out_hit(hit, side)                                       \
+               do {                                                    \
+                   hit = 1e10; /* default is 'way off' */              \
+                   if (classification[side]) {                         \
+                       /* Not both inside, i.e. there has to be an     \
+                        * intersection point: */                       \
+                       hit_in_poly = - whichside * p_side[side]        \
+                           / (e_side[0][side] - e_side[1][side]);      \
+                       cleanup_hit(hit_in_poly);                       \
+                       if (hit_in_edge(hit_in_poly))   {               \
+                           this_hit = e_side[0][side]                  \
+                               / (e_side[0][side] - e_side[1][side]);  \
+                           cleanup_hit(this_hit);                      \
+                           if (hit_in_edge(this_hit)) {                \
+                               hit = this_hit;                         \
+                           }                                           \
+                       }                                               \
+                   }                                                   \
+                   if (hit == 1e10)                                    \
+                       /* doesn't really intersect */                  \
+                       classification[side] = 0;                       \
+               } while (0)
+
+               check_out_hit(hit1, side1);
+               check_out_hit(hit2, side2);
+#undef check_out_hit
+
+               /* OK, now we know the position of all the hits that
+                * might be needed, given by their positions between
+                * the test edge's vertices. */
+
+               /* Macro: combine all the classifications into a
+                * single classification bitpattern, for easier
+                * listing of cases */
+#define makeclass(s2, s1, p) (16 * s2 + 4 * s1 + p)
+
+               full_class = makeclass(classification[side2],
+                                      classification[side1],
+                                      classification[3]);
+
+               if (!full_class)
+                   /* all suspected intersections cleared, already! */
+                   continue;
+
+               /* First sort out all cases where one endpoint is
+                * attributed to more than one intersecting
+                * plane. Only one of those can be 'the real'
+                * intersection of the edge with the surface of the
+                * shadow volume surface. The others are intersections
+                * with the planes defining the volumes, but outside
+                * the actual faces.
+                *
+                * This cuts down the 27 to 13 cases (000, 001, 002,
+                * 012, and their permutations). Start with v1: */
+               switch (full_class & makeclass(1, 1, 1)) {
+               case makeclass(0,1,1):
+                   /* v1 is out 'side1' and infront of 'p' */
+                   if (front_hit < hit1)
+                       classification[3] = 0;
+                   else
+                       classification[side1] = 0;
+                   break;
+
+               case makeclass(1,0,1):
+                   /* v1 is out 'side2' and infront of 'p' */
+                   if (front_hit < hit2)
+                       classification[3] = 0;
+                   else
+                       classification[side2] = 0;
+                   break;
+
+                   /* The following two cases never trigger, even in
+                    * a full run through all.dem. Neither do the
+                    * corresponding 2 cases for the vertex2
+                    * classification. I think this is so because the
+                    * determination of 'side1' and 'side2' already
+                    * took care of this: if the edge vertex is out
+                    * two sides, those two will _not_ be chosen as
+                    * 'side1' and 'side2', because one of them is
+                    * completely on one side of the edge.  */
+               case makeclass(1,1,0):
+                   /* v1 out both sides, but behind the front */
+                   if (hit1 < hit2)
+                       /* hit2 is closer to the hidden vertex v2, so
+                        * it's the relevant intersection: */
+                       classification[side1] = 0;
+                   else
+                       classification[side2] = 0;
+                   break;
+
+               case makeclass(1,1,1):
+                   /* v1 out both sides, and in front of p (--> v2 is
+                    * hidden) */
+                   if (front_hit < hit1) {
+                       classification[3] = 0;
+                       if (hit1 < hit2)
+                           classification[side1] = 0;
+                       else
+                           classification[side2] = 0;
+                   } else {
+                       classification[side1] = 0;
+                       if (front_hit < hit2)
+                           classification[3] = 0;
+                       else
+                           classification[side2] = 0;
+                   }
+                   break;
+
+               default:
+                   ; /* do nothing */
+               } /* switch(v1 classes) */
+
+
+               /* And the same, for v2 */
+               switch (full_class & makeclass(2, 2, 2)) {
+               case makeclass(0,2,2):
+                   /* v2 is out 'side1' and infront of 'p' */
+                   if (front_hit > hit1)
+                       classification[3] = 0;
+                   else
+                       classification[side1] = 0;
+                   break;
+
+               case makeclass(2,0,2):
+                   /* v2 out side 'side2' and infront of 'p' */
+                   if (front_hit > hit2)
+                       classification[3] = 0;
+                   else
+                       classification[side2] = 0;
+                   break;
+
+                   /* See note in v1 handling about these two cases
+                    * being impossible... */
+               case makeclass(2,2,0):
+                   /* v2 out both sides, but behind the front */
+                   if (hit1 > hit2)
+                       /* hit2 is closer to the hidden vertex v2, so
+                        * it's the relevant intersection: */
+                       classification[side1] = 0;
+                   else
+                       classification[side2] = 0;
+                   break;
+
+               case makeclass(2,2,2):
+                   /* v2 out both sides, and in front of p (--> v1 is
+                    * hidden) */
+                   if (front_hit > hit1) {
+                       classification[3] = 0;
+                       if (hit1 > hit2)
+                           classification[side1] = 0;
+                       else
+                           classification[side2] = 0;
+                   } else {
+                       classification[side1] = 0;
+                       if (front_hit > hit2)
+                           classification[3] = 0;
+                       else
+                           classification[side2] = 0;
+                   }
+                   break;
+
+               default:
+                   ; /* do nothing */
+               } /* switch(v2 classes) */
+
+
+               /* recalculate full_class after all these possible changes: */
+               full_class = makeclass(classification[side2],
+                                      classification[side1],
+                                      classification[3]);
+
+               /* This monster switch catches all the 13 remaining cases
+                * individually. */
+               switch (full_class) {
+               default:
+                   printf("in_front: (T9) class %d is illegal. Should never happen.\n",
+                          full_class);
+                   break;
+               case makeclass(0,0,0):
+                   /* can happen, by resetting of classification bits
+                    * inside this test */
+                   break;
+
+/*--------- The simplest cases first: only one intersection point -----*/
+                   /* Code is the same for all six cases, or nearly so: */
+#define handle_singleplane_hit(vert, hitpar)                               \
+                   newvert[0] = split_line_at_ratio(vnum1, vnum2, hitpar); \
+                   setup_edge(vert, newvert[0]);                           \
+                   break;
+
+               case makeclass(0,0,1): /* v1 is in front of poly: */
+                   handle_singleplane_hit(vnum1, front_hit);
+               case makeclass(0,0,2): /* v2 is in front of poly: */
+                   handle_singleplane_hit(vnum2, front_hit);
+               case makeclass(0,1,0): /* v1 is out side1: */
+                   handle_singleplane_hit(vnum1,hit1);
+               case makeclass(0,2,0): /* v2 is out side1: */
+                   handle_singleplane_hit(vnum2,hit1);
+               case makeclass(1,0,0): /* v1 is out side2 */
+                   handle_singleplane_hit(vnum1,hit2);
+               case makeclass(2,0,0): /* v2 is out side2 */
+                   handle_singleplane_hit(vnum2,hit2);
+
+/*----------- cases with 2 certain intersections: --------------*/
+               case makeclass(1,2,0):
+               case makeclass(2,1,0):
+                   /* Both behind the front, v1 out one side, v2 out
+                    * the other.  These two cases can be handled by
+                    * common code: */
+                   newvert[0] = split_line_at_ratio(vnum1, vnum2, hit1);
+                   newvert[1] = split_line_at_ratio(vnum1, vnum2, hit2);
+                   if (hit1 < hit2) {
+                       /* the fragments are v1--hit1 and hit2--v2 */
+                       handle_edge_fragment(edgenum, newvert[1], vnum2, polynum);
+                       setup_edge(vnum1, newvert[0]);
+                   } else {
+                       /* the fragments are v2--hit1 and hit2--v1 */
+                       handle_edge_fragment(edgenum, vnum1, newvert[1], polynum);
+                       setup_edge(newvert[0], vnum2);
+                   }
+                   break;
+
+/*----------- cases with either 2 or no intersections: --------------*/
+
+                   /* Mainly identical code block to be used 4 times
+                    * --> macro */
+                   /* HBB 20001108: up until today, this part of the
+                    * was severely buggy. But I do think I've got it
+                    * fixed up, this time */
+#define handle_outside_behind_vs_infront(hitvar1, hitvar2)                        \
+                   /* vnum1 out plane of 'hitvar1' and in back, vnum2             \
+                     * in front */                                                \
+                   if ((hitvar1) < (hitvar2)) {                                   \
+                       newvert[0] = split_line_at_ratio(vnum1, vnum2, (hitvar1)); \
+                       newvert[1] = split_line_at_ratio(vnum1, vnum2, (hitvar2)); \
+                       handle_edge_fragment(edgenum, newvert[1], vnum2, polynum); \
+                       setup_edge(vnum1, newvert[0]);                             \
+                   }                                                              \
+                   /* otherwise, the edge missed the shadow volume                \
+                    * --> nothing to do */                                        \
+                   break;
+
+               case makeclass(0,1,2): /* v1 out side1 and in back, v2 in front */
+                   handle_outside_behind_vs_infront(hit1, front_hit);
+
+               case makeclass(0,2,1): /* v2 out side1 and in back, v1 in front */
+                   handle_outside_behind_vs_infront(front_hit, hit1);
+
+               case makeclass(1,0,2): /* v1 out side2 and in back, v2 in front */
+                   handle_outside_behind_vs_infront(hit2, front_hit);
+
+               case makeclass(2,0,1): /* v2 out side2 and in back, v1 in front */
+                   handle_outside_behind_vs_infront(front_hit, hit2);
+
+#undef handle_outside_behind_vs_infront
+
+               } /* end of switch through the cases */
+
+           } /* end of part 'T9' */
+       } /* for (polygons in list) */
+
+    /* Came here, so there's something left of this edge, which needs
+     * to be drawn.  But the vertices are different, now, so copy our
+     * new vertices back into 'e' */
+
+    draw_edge(elist + edgenum, vlist + vnum1, vlist + vnum2);
+
+    return 1;
+}
+
+
+/* HBB 20000617: reimplemented this routine from scratch */
+/* Externally callable function to draw a line, but hide it behind the
+ * visible surface. */
+/* NB: The p_vertex arguments are not allowed to be pointers into the
+ * hidden3d 'vlist' structure. If they are, they may become invalid
+ * before they're used, because of the nextfrom_dynarray() call. */
+void
+draw_line_hidden(
+    p_vertex v1, p_vertex v2,  /* pointers to the end vertices */
+    struct lp_style_type *lp)  /* line and point style to draw in */
+{
+    long int vstore1, vstore2;
+    long int edgenum;
+    long int temp_pfirst;
+
+    /* If no polygons have been stored, nothing can be hidden, and we
+     * can't use in_front() because the datastructures are partly
+     * invalid. So just draw the line and be done with it */
+    if (!polygons.end) {
+       draw3d_line_unconditional(v1, v2, lp, lp->l_type);
+       return;
+    }
+
+    /* Copy two vertices into hidden3d arrays: */
+    nextfrom_dynarray(&vertices);
+    vstore1 = vertices.end - 1;
+    vlist[vstore1] = *v1;
+    if (v2) {
+       vlist[vstore1].lp_style = NULL;
+       nextfrom_dynarray(&vertices);
+       vstore2 = vertices.end - 1;
+       vlist[vstore2] = *v2;
+       vlist[vstore2].lp_style = NULL;
+    } else {
+       /* v2 == NULL --> this is a point symbol to be drawn. Make two
+        * vertex pointers the same, and set up the 'style' field */
+       vstore2 = vstore1;
+       vlist[vstore2].lp_style = lp;
+    }
+
+    /* store the edge into the hidden3d datastructures */
+    edgenum = make_edge(vstore1, vstore2, lp, lp->l_type, -1);
+
+    /* remove hidden portions of the line, and draw what remains */
+    temp_pfirst = pfirst;
+    in_front(edgenum, elist[edgenum].v1, elist[edgenum].v2, &temp_pfirst);
+
+    /* release allocated storage slots: */
+    droplast_dynarray(&edges);
+    droplast_dynarray(&vertices);
+    if (v2)
+       droplast_dynarray(&vertices);
+}
+
+/***********************************************************************
+ * and, finally, the 'mother function' that uses all these lots of tools
+ ***********************************************************************/
+void
+plot3d_hidden(struct surface_points *plots, int pcount)
+{
+    /* make vertices, edges and polygons out of all the plots */
+    build_networks(plots, pcount);
+
+    if (! edges.end) {
+       /* No drawable edges found. Free all storage and bail out. */
+       term_hidden_line_removal();
+       graph_error("*All* edges undefined or out of range, thus no plot.");
+    }
+
+    if (! polygons.end) {
+       /* No polygons anything could be hidden behind... */
+
+       sort_edges_by_z();
+       while (efirst >= 0) {
+           draw_edge(elist+efirst, vlist + elist[efirst].v1, vlist + elist[efirst].v2);
+           efirst = elist[efirst].next;
+       }
+    } else {
+       long int temporary_pfirst;
+
+       /* Presort edges in z order */
+       sort_edges_by_z();
+       /* Presort polygons in z order */
+       sort_polys_by_z();
+
+       temporary_pfirst = pfirst;
+
+       while (efirst >=0) {
+           if (elist[efirst].style >= -2) /* skip invisible edges */
+               in_front(efirst, elist[efirst].v1, elist[efirst].v2,
+                        &temporary_pfirst);
+           efirst = elist[efirst].next;
+       }
+    }
+
+    /* Free memory */
+    /* FIXME: anything to free? */
+}
+
+void
+reset_hidden3doptions()
+{
+    hiddenBacksideLinetypeOffset = BACKSIDE_LINETYPE_OFFSET;
+    hiddenTriangleLinesdrawnPattern = TRIANGLE_LINESDRAWN_PATTERN;
+    hiddenHandleUndefinedPoints = HANDLE_UNDEFINED_POINTS;
+    hiddenShowAlternativeDiagonal = SHOW_ALTERNATIVE_DIAGONAL;
+    hiddenHandleBentoverQuadrangles = HANDLE_BENTOVER_QUADRANGLES;
+}
+
+/* Emacs editing help for HBB:
+ * Local Variables: ***
+ * c-basic-offset: 4 ***
+ * End: ***
+ */