//      Functions to shade regions on the basis of value.
//      Can be used to shade contour plots or alone.
//      Copyright 1993 Wesley Ebisuzaki
//
// Copyright (C) 2004-2014 Alan W. Irwin
//
// This file is part of PLplot.
//
// PLplot is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License as published
// by the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// PLplot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public License
// along with PLplot; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//

//--------------------------------------------------------------------------
// Call syntax for plshade():
//
// void plshade(PLFLT *a, PLINT nx, PLINT ny, char *defined,
//	PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax,
//      PLFLT shade_min, PLFLT shade_max,
//      PLINT sh_color, PLFLT sh_width, PLINT min_color, PLFLT min_width,
//      PLINT max_color, PLFLT max_width, void (*fill)(), PLINT
//      rectangular, ...)
//
// arguments:
//
//      PLFLT &(a[0][0])
//
// Contains array to be plotted. The array must have been declared as
// PLFLT a[nx][ny].  See following note on fortran-style arrays.
//
//      PLINT nx, ny
//
// Dimension of array "a".
//
//      char &(defined[0][0])
//
// Contains array of flags, 1 = data is valid, 0 = data is not valid.
// This array determines which sections of the data is to be plotted.
// This argument can be NULL if all the values are valid.  Must have been
// declared as char defined[nx][ny].
//
//      PLFLT xmin, xmax, ymin, ymax
//
// Defines the "grid" coordinates.  The data a[0][0] has a position of
// (xmin,ymin).
//
//      void (*mapform)()
//
// Transformation from `grid' coordinates to world coordinates.  This
// pointer to a function can be NULL in which case the grid coordinates
// are the same as the world coordinates.
//
//      PLFLT shade_min, shade_max
//
// Defines the interval to be shaded. If shade_max <= shade_min, plshade
// does nothing.
//
//	PLINT sh_cmap, PLFLT sh_color, PLFLT sh_width
//
// Defines color map, color map index, and width used by the fill pattern.
//
//      PLINT min_color, PLFLT min_width, PLINT max_color, PLFLT max_width
//
// Defines pen color, width used by the boundary of shaded region. The min
// values are used for the shade_min boundary, and the max values are used
// on the shade_max boundary.  Set color and width to zero for no plotted
// boundaries.
//
//      void (*fill)()
//
// Routine used to fill the region.  Use plfill.  Future version of plplot
// may have other fill routines.
//
//      PLINT rectangular
//
// Flag. Set to 1 if rectangles map to rectangles after (*mapform)() else
// set to zero. If rectangular is set to 1, plshade tries to save time by
// filling large rectangles.  This optimization fails if (*mapform)()
// distorts the shape of rectangles.  For example a plot in polor
// coordinates has to have rectangular set to zero.
//
// Example mapform's:
//
// Grid to world coordinate transformation.
// This example goes from a r-theta to x-y for a polar plot.
//
// void mapform(PLINT n, PLFLT *x, PLFLT *y) {
//      int i;
//      double r, theta;
//      for (i = 0; i < n; i++) {
//          r = x[i];
//          theta = y[i];
//          x[i] = r*cos(theta);
//          y[i] = r*sin(theta);
//      }
// }
//
// Grid was in cm, convert to world coordinates in inches.
// Expands in x direction.
//
// void mapform(PLINT n, PLFLT *x, PLFLT *y) {
//      int i;
//      for (i = 0; i < n; i++) {
//              x[i] = (1.0 / 2.5) * x[i];
//              y[i] = (1.0 / 2.5) * y[i];
//      }
// }
//
//--------------------------------------------------------------------------

#include "plplotP.h"
#include <float.h>

#define NEG                  1
#define POS                  8
#define OK                   0
#define UNDEF                64
#define NUMBER_BISECTIONS    10

#define linear( val1, val2, level )    ( ( level - val1 ) / ( val2 - val1 ) )

// Global variables

static PLFLT sh_max, sh_min;
static int   min_points, max_points, n_point;
static int   min_pts[4], max_pts[4];
static PLINT pen_col_min, pen_col_max;
static PLFLT pen_wd_min, pen_wd_max;
static PLFLT int_val;

// Function prototypes

static void
set_cond( register int *cond, register PLFLT *a, register PLINT n );

static int
find_interval( PLFLT a0, PLFLT a1, PLINT c0, PLINT c1, PLFLT *x );

static void
selected_polygon( PLFILL_callback fill, PLDEFINED_callback defined,
                  PLFLT_VECTOR x, PLFLT_VECTOR y, PLINT v1, PLINT v2, PLINT v3, PLINT v4 );

static void
exfill( PLFILL_callback fill, PLDEFINED_callback defined,
        int n, PLFLT_VECTOR x, PLFLT_VECTOR y );

static void
big_recl( int *cond_code, register int ny, int dx, int dy,
          int *ix, int *iy );

static void
draw_boundary( PLINT slope, PLFLT *x, PLFLT *y );

static PLINT
plctest( PLFLT *x, PLFLT level );

static PLINT
plctestez( PLFLT *a, PLINT nx, PLINT ny, PLINT ix,
           PLINT iy, PLFLT level );

static void
plshade_int( PLF2EVAL_callback f2eval, PLPointer f2eval_data,
             PLF2EVAL_callback c2eval, PLPointer c2eval_data,
             PLDEFINED_callback defined,
             PLINT nx, PLINT ny,
             PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax,
             PLFLT shade_min, PLFLT shade_max,
             PLINT sh_cmap, PLFLT sh_color, PLFLT sh_width,
             PLINT min_color, PLFLT min_width,
             PLINT max_color, PLFLT max_width,
             PLFILL_callback fill, PLINT rectangular,
             PLTRANSFORM_callback pltr, PLPointer pltr_data );

// N.B. This routine only needed by the Fortran interface to distinguish
// the case where pltr and pltr_data are NULL.  So don't put declaration in
// header which might encourage others to use this in some other context.
PLDLLIMPEXP void
plshades_null( PLFLT_MATRIX a, PLINT nx, PLINT ny, PLDEFINED_callback defined,
               PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax,
               PLFLT_VECTOR clevel, PLINT nlevel, PLFLT fill_width,
               PLINT cont_color, PLFLT cont_width,
               PLFILL_callback fill, PLINT rectangular )
{
    plfshades( plf2ops_c(), (PLPointer) a, nx, ny, defined,
        xmin, xmax, ymin, ymax,
        clevel, nlevel, fill_width,
        cont_color, cont_width,
        fill, rectangular,
        NULL, NULL );
}

//--------------------------------------------------------------------------
// plshades()
//
// Shade regions via a series of calls to plshade.
// All arguments are the same as plshade except the following:
// clevel is a pointer to an array of values representing
// the shade edge values, nlevel-1 is
// the number of different shades, (nlevel is the number of shade edges),
// fill_width is the pattern fill width, and cont_color and cont_width
// are the color and width of the contour drawn at each shade edge.
// (if cont_color <= 0 or cont_width <=0, no such contours are drawn).
//--------------------------------------------------------------------------

void c_plshades( PLFLT_MATRIX a, PLINT nx, PLINT ny, PLDEFINED_callback defined,
                 PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax,
                 PLFLT_VECTOR clevel, PLINT nlevel, PLFLT fill_width,
                 PLINT cont_color, PLFLT cont_width,
                 PLFILL_callback fill, PLINT rectangular,
                 PLTRANSFORM_callback pltr, PLPointer pltr_data )
{
    plfshades( plf2ops_c(), (PLPointer) a, nx, ny, defined,
        xmin, xmax, ymin, ymax,
        clevel, nlevel, fill_width,
        cont_color, cont_width,
        fill, rectangular,
        pltr, pltr_data );
}

//--------------------------------------------------------------------------
// plfshades()
//
// Shade regions via a series of calls to plfshade1.
// All arguments are the same as plfshade1 except the following:
// clevel is a pointer to an array of values representing
// the shade edge values, nlevel-1 is
// the number of different shades, (nlevel is the number of shade edges),
// fill_width is the pattern fill width, and cont_color and cont_width
// are the color and width of the contour drawn at each shade edge.
// (if cont_color <= 0 or cont_width <=0, no such contours are drawn).
//--------------------------------------------------------------------------

void
plfshades( PLF2OPS zops, PLPointer zp, PLINT nx, PLINT ny,
           PLDEFINED_callback defined,
           PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax,
           PLFLT_VECTOR clevel, PLINT nlevel, PLFLT fill_width,
           PLINT cont_color, PLFLT cont_width,
           PLFILL_callback fill, PLINT rectangular,
           PLTRANSFORM_callback pltr, PLPointer pltr_data )
{
    PLFLT shade_min, shade_max, shade_color;
    PLINT i, init_color;
    PLFLT init_width, color_min, color_max, color_range;

    // Color range to use
    color_min   = plsc->cmap1_min;
    color_max   = plsc->cmap1_max;
    color_range = color_max - color_min;

    for ( i = 0; i < nlevel - 1; i++ )
    {
        shade_min   = clevel[i];
        shade_max   = clevel[i + 1];
        shade_color = color_min + i / (PLFLT) ( nlevel - 2 ) * color_range;
        // The constants in order mean
        // (1) color map1,
        // (0, 0, 0, 0) all edge effects will be done with plcont rather
        // than the normal plshade drawing which gets partially blocked
        // when sequential shading is done as in the present case

        plfshade1( zops, zp, nx, ny, defined, xmin, xmax, ymin, ymax,
            shade_min, shade_max,
            1, shade_color, fill_width,
            0, 0, 0, 0,
            fill, rectangular, pltr, pltr_data );
    }
    if ( cont_color > 0 && cont_width > 0 )
    {
        init_color = plsc->icol0;
        init_width = plsc->width;
        plcol0( cont_color );
        plwidth( cont_width );
        if ( pltr )
        {
            plfcont( zops->f2eval, zp, nx, ny, 1, nx, 1, ny, clevel, nlevel, pltr, pltr_data );
        }
        else
        {
            // For this case use the same interpretation that occurs internally
            // for plshade.  That is set up x and y grids that map from the
            // index ranges to xmin, xmax, ymin, ymax, and use those grids
            // for the plcont call.
            //
            PLcGrid cgrid1;
            PLFLT   *x, *y;
            cgrid1.nx = nx;
            cgrid1.ny = ny;
            x         = (PLFLT *) malloc( (size_t) nx * sizeof ( PLFLT ) );
            if ( x == NULL )
                plexit( "plfshades: Out of memory for x" );
            cgrid1.xg = x;
            for ( i = 0; i < nx; i++ )
                cgrid1.xg[i] = xmin + ( xmax - xmin ) * (float) i / (float) ( nx - 1 );
            y = (PLFLT *) malloc( (size_t) ny * sizeof ( PLFLT ) );
            if ( y == NULL )
                plexit( "plfshades: Out of memory for y" );
            cgrid1.yg = y;
            for ( i = 0; i < ny; i++ )
                cgrid1.yg[i] = ymin + ( ymax - ymin ) * (float) i / (float) ( ny - 1 );
            plfcont( zops->f2eval, zp, nx, ny, 1, nx, 1, ny, clevel, nlevel,
                pltr1, (void *) &cgrid1 );
            free( x );
            free( y );
        }
        plcol0( init_color );
        plwidth( init_width );
    }
}

// N.B. This routine only needed by the Fortran interface to distinguish
// the case where pltr and pltr_data are NULL.  So don't put declaration in
// header which might encourage others to use this in some other context.
PLDLLIMPEXP void
plshade_null( PLFLT_MATRIX a, PLINT nx, PLINT ny, PLDEFINED_callback defined,
              PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax,
              PLFLT shade_min, PLFLT shade_max,
              PLINT sh_cmap, PLFLT sh_color, PLFLT sh_width,
              PLINT min_color, PLFLT min_width,
              PLINT max_color, PLFLT max_width,
              PLFILL_callback fill, PLINT rectangular )
{
    plshade_int( plf2eval1, (PLPointer) a,
        NULL, NULL,
//	     plc2eval, (PLPointer) &cgrid,
        defined, nx, ny, xmin,
        xmax, ymin, ymax, shade_min, shade_max,
        sh_cmap, sh_color, sh_width,
        min_color, min_width, max_color, max_width,
        fill, rectangular, NULL, NULL );
}

//--------------------------------------------------------------------------
// plshade()
//
// Shade region.
// This interface to plfshade() assumes the 2d function array is passed
// via a (PLFLT **), and is column-dominant (normal C ordering).
//--------------------------------------------------------------------------

void c_plshade( PLFLT_MATRIX a, PLINT nx, PLINT ny, PLDEFINED_callback defined,
                PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax,
                PLFLT shade_min, PLFLT shade_max,
                PLINT sh_cmap, PLFLT sh_color, PLFLT sh_width,
                PLINT min_color, PLFLT min_width,
                PLINT max_color, PLFLT max_width,
                PLFILL_callback fill, PLINT rectangular,
                PLTRANSFORM_callback pltr, PLPointer pltr_data )
{
    plshade_int( plf2eval1, (PLPointer) a,
        NULL, NULL,
//	     plc2eval, (PLPointer) &cgrid,
        defined, nx, ny, xmin,
        xmax, ymin, ymax, shade_min, shade_max,
        sh_cmap, sh_color, sh_width,
        min_color, min_width, max_color, max_width,
        fill, rectangular, pltr, pltr_data );
}

//--------------------------------------------------------------------------
// plshade1()
//
// Shade region.
// This interface to plfshade() assumes the 2d function array is passed
// via a (PLFLT *), and is column-dominant (normal C ordering).
//--------------------------------------------------------------------------

void c_plshade1( PLFLT_VECTOR a, PLINT nx, PLINT ny, PLDEFINED_callback defined,
                 PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax,
                 PLFLT shade_min, PLFLT shade_max,
                 PLINT sh_cmap, PLFLT sh_color, PLFLT sh_width,
                 PLINT min_color, PLFLT min_width,
                 PLINT max_color, PLFLT max_width,
                 PLFILL_callback fill, PLINT rectangular,
                 PLTRANSFORM_callback pltr, PLPointer pltr_data )
{
    PLfGrid grid;

    grid.f  = a;
    grid.nx = nx;
    grid.ny = ny;

    plshade_int( plf2eval, ( PLPointer ) & grid,
        NULL, NULL,
//	     plc2eval, (PLPointer) &cgrid,
        defined, nx, ny, xmin,
        xmax, ymin, ymax, shade_min, shade_max,
        sh_cmap, sh_color, sh_width,
        min_color, min_width, max_color, max_width,
        fill, rectangular, pltr, pltr_data );
}

//--------------------------------------------------------------------------
// plfshade()
//
// Shade region.
// Array values are determined by the input function and the passed data.
//--------------------------------------------------------------------------

void
plfshade( PLF2EVAL_callback f2eval, PLPointer f2eval_data,
          PLF2EVAL_callback c2eval, PLPointer c2eval_data,
          PLINT nx, PLINT ny,
          PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax,
          PLFLT shade_min, PLFLT shade_max,
          PLINT sh_cmap, PLFLT sh_color, PLFLT sh_width,
          PLINT min_color, PLFLT min_width,
          PLINT max_color, PLFLT max_width,
          PLFILL_callback fill, PLINT rectangular,
          PLTRANSFORM_callback pltr, PLPointer pltr_data )
{
    plshade_int( f2eval, f2eval_data, c2eval, c2eval_data,
        NULL,
        nx, ny, xmin, xmax, ymin, ymax,
        shade_min, shade_max, sh_cmap, sh_color, sh_width,
        min_color, min_width, max_color, max_width,
        fill, rectangular, pltr, pltr_data );
}

//--------------------------------------------------------------------------
// plfshade1()
//
// Shade region.
//
// This function is a plf2ops variant of c_plfshade and c_plfshade1.  It
// differs from plfshade in that it supports a "defined" callback (like
// c_plshade and c_plfshade1) rather than a "defined mask" (like plfshade
// even though it is not yet implemented).
//--------------------------------------------------------------------------

void
plfshade1( PLF2OPS zops, PLPointer zp, PLINT nx, PLINT ny,
           PLDEFINED_callback defined,
           PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax,
           PLFLT shade_min, PLFLT shade_max,
           PLINT sh_cmap, PLFLT sh_color, PLFLT sh_width,
           PLINT min_color, PLFLT min_width,
           PLINT max_color, PLFLT max_width,
           PLFILL_callback fill, PLINT rectangular,
           PLTRANSFORM_callback pltr, PLPointer pltr_data )
{
    plshade_int( zops->f2eval, zp,
        NULL, NULL,
//	     plc2eval, (PLPointer) &cgrid,
        defined, nx, ny, xmin,
        xmax, ymin, ymax, shade_min, shade_max,
        sh_cmap, sh_color, sh_width,
        min_color, min_width, max_color, max_width,
        fill, rectangular, pltr, pltr_data );
}

//--------------------------------------------------------------------------
// plshade_int()
//
// Shade region -- this routine does all the work
//
// This routine is internal so the arguments can and will change.
// To retain some compatibility between versions, you must go through
// some stub routine!
//
// 4/95
//
// parameters:
//
// f2eval, f2eval_data:  data to plot
// defined: defined mask (old API - implimented)
// nx, ny: array dimensions
// xmin, xmax, ymin, ymax: grid coordinates
// shade_min, shade_max: shade region with values between ...
// sh_cmap, sh_color, sh_width: shading parameters, width is only for hatching
// min_color, min_width: line parameters for boundary (minimum)
// max_color, max_width: line parameters for boundary (maximum)
//     set min_width == 0 and max_width == 0 for no contours
// fill: fill function, set to NULL for no shading (contour plot)
// rectangular: flag set to 1 if pltr() maps rectangles to rectangles
//     this helps optimize the plotting
// pltr: function to map from grid to plot coordinates
//
//
//--------------------------------------------------------------------------

static void
plshade_int( PLF2EVAL_callback f2eval, PLPointer f2eval_data,
             PLF2EVAL_callback c2eval, PLPointer PL_UNUSED( c2eval_data ), //c2eval is unused.
             PLDEFINED_callback defined,
             PLINT nx, PLINT ny,
             PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax,
             PLFLT shade_min, PLFLT shade_max,
             PLINT sh_cmap, PLFLT sh_color, PLFLT sh_width,
             PLINT min_color, PLFLT min_width,
             PLINT max_color, PLFLT max_width,
             PLFILL_callback fill, PLINT rectangular,
             PLTRANSFORM_callback pltr, PLPointer pltr_data )
{
    PLINT n, slope = 0, ix, iy;
    int   count, i, j, nxny;
    PLFLT *a, *a0, *a1, dx, dy;
    PLFLT x[8], y[8], xp[2], tx, ty, init_width;
    int   *c, *c0, *c1;

    (void) c2eval;   // Cast to void to silence compiler warning about unused parameter

    if ( plsc->level < 3 )
    {
        plabort( "plfshade: window must be set up first" );
        return;
    }

    if ( nx <= 0 || ny <= 0 )
    {
        plabort( "plfshade: nx and ny must be positive" );
        return;
    }

    if ( shade_min >= shade_max )
    {
        plabort( "plfshade: shade_max must exceed shade_min" );
        return;
    }

    if ( pltr == NULL && plsc->coordinate_transform == NULL )
        rectangular = 1;

    int_val    = shade_max - shade_min;
    init_width = plsc->width;

    pen_col_min = min_color;
    pen_col_max = max_color;

    pen_wd_min = min_width;
    pen_wd_max = max_width;

    plstyl( (PLINT) 0, NULL, NULL );
    plwidth( sh_width );
    if ( fill != NULL )
    {
        switch ( sh_cmap )
        {
        case 0:
            plcol0( (PLINT) sh_color );
            break;
        case 1:
            plcol1( sh_color );
            break;
        default:
            plabort( "plfshade: invalid color map selection" );
            return;
        }
    }
    // alloc space for value array, and initialize
    // This is only a temporary kludge
    nxny = nx * ny;
    if ( ( a = (PLFLT *) malloc( (size_t) nxny * sizeof ( PLFLT ) ) ) == NULL )
    {
        plabort( "plfshade: unable to allocate memory for value array" );
        return;
    }

    for ( ix = 0; ix < nx; ix++ )
        for ( iy = 0; iy < ny; iy++ )
            a[iy + ix * ny] = f2eval( ix, iy, f2eval_data );

    // alloc space for condition codes

    if ( ( c = (int *) malloc( (size_t) nxny * sizeof ( int ) ) ) == NULL )
    {
        plabort( "plfshade: unable to allocate memory for condition codes" );
        free( a );
        return;
    }

    sh_min = shade_min;
    sh_max = shade_max;

    set_cond( c, a, nxny );
    dx = ( xmax - xmin ) / ( nx - 1 );
    dy = ( ymax - ymin ) / ( ny - 1 );
    a0 = a;
    a1 = a + ny;
    c0 = c;
    c1 = c + ny;

    for ( ix = 0; ix < nx - 1; ix++ )
    {
        for ( iy = 0; iy < ny - 1; iy++ )
        {
            count = c0[iy] + c0[iy + 1] + c1[iy] + c1[iy + 1];

            // No filling needs to be done for these cases

            if ( count >= UNDEF )
                continue;
            if ( count == 4 * POS )
                continue;
            if ( count == 4 * NEG )
                continue;

            // Entire rectangle can be filled

            if ( count == 4 * OK )
            {
                // find biggest rectangle that fits
                if ( rectangular )
                {
                    big_recl( c0 + iy, ny, nx - ix, ny - iy, &i, &j );
                }
                else
                {
                    i = j = 1;
                }
                x[0] = x[1] = ix;
                x[2] = x[3] = ix + i;
                y[0] = y[3] = iy;
                y[1] = y[2] = iy + j;

                if ( pltr )
                {
                    for ( i = 0; i < 4; i++ )
                    {
                        ( *pltr )( x[i], y[i], &tx, &ty, pltr_data );
                        x[i] = tx;
                        y[i] = ty;
                    }
                }
                else
                {
                    for ( i = 0; i < 4; i++ )
                    {
                        x[i] = xmin + x[i] * dx;
                        y[i] = ymin + y[i] * dy;
                    }
                }
                if ( fill != NULL )
                    exfill( fill, defined, (PLINT) 4, x, y );
                iy += j - 1;
                continue;
            }

            // Only part of rectangle can be filled

            n_point = min_points = max_points = 0;
            n       = find_interval( a0[iy], a0[iy + 1], c0[iy], c0[iy + 1], xp );
            for ( j = 0; j < n; j++ )
            {
                x[j] = ix;
                y[j] = iy + xp[j];
            }

            i = find_interval( a0[iy + 1], a1[iy + 1],
                c0[iy + 1], c1[iy + 1], xp );

            for ( j = 0; j < i; j++ )
            {
                x[j + n] = ix + xp[j];
                y[j + n] = iy + 1;
            }
            n += i;

            i = find_interval( a1[iy + 1], a1[iy], c1[iy + 1], c1[iy], xp );
            for ( j = 0; j < i; j++ )
            {
                x[n + j] = ix + 1;
                y[n + j] = iy + 1 - xp[j];
            }
            n += i;

            i = find_interval( a1[iy], a0[iy], c1[iy], c0[iy], xp );
            for ( j = 0; j < i; j++ )
            {
                x[n + j] = ix + 1 - xp[j];
                y[n + j] = iy;
            }
            n += i;

            if ( pltr )
            {
                for ( i = 0; i < n; i++ )
                {
                    ( *pltr )( x[i], y[i], &tx, &ty, pltr_data );
                    x[i] = tx;
                    y[i] = ty;
                }
            }
            else
            {
                for ( i = 0; i < n; i++ )
                {
                    x[i] = xmin + x[i] * dx;
                    y[i] = ymin + y[i] * dy;
                }
            }

            if ( min_points == 4 )
                slope = plctestez( a, nx, ny, ix, iy, shade_min );
            if ( max_points == 4 )
                slope = plctestez( a, nx, ny, ix, iy, shade_max );

            // n = number of end of line segments
            // min_points = number times shade_min meets edge
            // max_points = number times shade_max meets edge

            // special cases: check number of times a contour is in a box

            switch ( ( min_points << 3 ) + max_points )
            {
            case 000:
            case 020:
            case 002:
            case 022:
                if ( fill != NULL && n > 0 )
                    exfill( fill, defined, n, x, y );
                break;
            case 040:   // 2 contour lines in box
            case 004:
                if ( n != 6 )
                    fprintf( stderr, "plfshade err n=%d !6", (int) n );
                if ( slope == 1 && c0[iy] == OK )
                {
                    if ( fill != NULL )
                        exfill( fill, defined, n, x, y );
                }
                else if ( slope == 1 )
                {
                    selected_polygon( fill, defined, x, y, 0, 1, 2, -1 );
                    selected_polygon( fill, defined, x, y, 3, 4, 5, -1 );
                }
                else if ( c0[iy + 1] == OK )
                {
                    if ( fill != NULL )
                        exfill( fill, defined, n, x, y );
                }
                else
                {
                    selected_polygon( fill, defined, x, y, 0, 1, 5, -1 );
                    selected_polygon( fill, defined, x, y, 2, 3, 4, -1 );
                }
                break;
            case 044:
                if ( n != 8 )
                    fprintf( stderr, "plfshade err n=%d !8", (int) n );
                if ( slope == 1 )
                {
                    selected_polygon( fill, defined, x, y, 0, 1, 2, 3 );
                    selected_polygon( fill, defined, x, y, 4, 5, 6, 7 );
                }
                else
                {
                    selected_polygon( fill, defined, x, y, 0, 1, 6, 7 );
                    selected_polygon( fill, defined, x, y, 2, 3, 4, 5 );
                }
                break;
            case 024:
            case 042:
                // 3 contours
                if ( n != 7 )
                    fprintf( stderr, "plfshade err n=%d !7", (int) n );

                if ( ( c0[iy] == OK || c1[iy + 1] == OK ) && slope == 1 )
                {
                    if ( fill != NULL )
                        exfill( fill, defined, n, x, y );
                }
                else if ( ( c0[iy + 1] == OK || c1[iy] == OK ) && slope == 0 )
                {
                    if ( fill != NULL )
                        exfill( fill, defined, n, x, y );
                }

                else if ( c0[iy] == OK )
                {
                    selected_polygon( fill, defined, x, y, 0, 1, 6, -1 );
                    selected_polygon( fill, defined, x, y, 2, 3, 4, 5 );
                }
                else if ( c0[iy + 1] == OK )
                {
                    selected_polygon( fill, defined, x, y, 0, 1, 2, -1 );
                    selected_polygon( fill, defined, x, y, 3, 4, 5, 6 );
                }
                else if ( c1[iy + 1] == OK )
                {
                    selected_polygon( fill, defined, x, y, 0, 1, 5, 6 );
                    selected_polygon( fill, defined, x, y, 2, 3, 4, -1 );
                }
                else if ( c1[iy] == OK )
                {
                    selected_polygon( fill, defined, x, y, 0, 1, 2, 3 );
                    selected_polygon( fill, defined, x, y, 4, 5, 6, -1 );
                }
                else
                {
                    fprintf( stderr, "plfshade err logic case 024:042\n" );
                }
                break;
            default:
                fprintf( stderr, "prog err switch\n" );
                break;
            }
            draw_boundary( slope, x, y );

            if ( fill != NULL )
            {
                plwidth( sh_width );
                if ( sh_cmap == 0 )
                    plcol0( (PLINT) sh_color );
                else if ( sh_cmap == 1 )
                    plcol1( sh_color );
            }
        }

        a0  = a1;
        c0  = c1;
        a1 += ny;
        c1 += ny;
    }

    free( c );
    free( a );
    plwidth( init_width );
}

//--------------------------------------------------------------------------
// set_cond()
//
// Fills out condition code array.
//--------------------------------------------------------------------------

static void
set_cond( register int *cond, register PLFLT *a, register PLINT n )
{
    while ( n-- )
    {
        if ( *a < sh_min )
            *cond++ = NEG;
        else if ( *a > sh_max )
            *cond++ = POS;
        else if ( isnan( *a ) ) //check for nans and set cond to undefined
            *cond++ = UNDEF;
        else
            *cond++ = OK;
        a++;
    }
}

//--------------------------------------------------------------------------
// find_interval()
//
// Two points x(0) = a0, (condition code c0) x(1) = a1, (condition code c1)
// return interval on the line that are shaded
//
// returns 0 : no points to be shaded 1 : x[0] <= x < 1 is the interval 2 :
// x[0] <= x <= x[1] < 1 interval to be shaded n_point, max_points,
// min_points are incremented location of min/max_points are stored
//--------------------------------------------------------------------------

static int
find_interval( PLFLT a0, PLFLT a1, PLINT c0, PLINT c1, PLFLT *x )
{
    register int n;

    n = 0;
    if ( c0 == OK )
    {
        x[n++] = 0.0;
        n_point++;
    }
    if ( c0 == c1 )
        return n;

    if ( c0 == NEG || c1 == POS )
    {
        if ( c0 == NEG )
        {
            x[n++] = linear( a0, a1, sh_min );
            min_pts[min_points++] = n_point++;
        }
        if ( c1 == POS )
        {
            x[n++] = linear( a0, a1, sh_max );
            max_pts[max_points++] = n_point++;
        }
    }
    if ( c0 == POS || c1 == NEG )
    {
        if ( c0 == POS )
        {
            x[n++] = linear( a0, a1, sh_max );
            max_pts[max_points++] = n_point++;
        }
        if ( c1 == NEG )
        {
            x[n++] = linear( a0, a1, sh_min );
            min_pts[min_points++] = n_point++;
        }
    }
    return n;
}

//--------------------------------------------------------------------------
// selected_polygon()
//
// Draws a polygon from points in x[] and y[].
// Point selected by v1..v4
//--------------------------------------------------------------------------

static void
selected_polygon( PLFILL_callback fill, PLDEFINED_callback defined,
                  PLFLT_VECTOR x, PLFLT_VECTOR y, PLINT v1, PLINT v2, PLINT v3, PLINT v4 )
{
    register PLINT n = 0;
    PLFLT          xx[4], yy[4];

    if ( fill == NULL )
        return;
    if ( v1 >= 0 )
    {
        xx[n]   = x[v1];
        yy[n++] = y[v1];
    }
    if ( v2 >= 0 )
    {
        xx[n]   = x[v2];
        yy[n++] = y[v2];
    }
    if ( v3 >= 0 )
    {
        xx[n]   = x[v3];
        yy[n++] = y[v3];
    }
    if ( v4 >= 0 )
    {
        xx[n]   = x[v4];
        yy[n++] = y[v4];
    }
    exfill( fill, defined, n, (PLFLT *) xx, (PLFLT *) yy );
}

//--------------------------------------------------------------------------
// bisect()
//
// Find boundary recursively by bisection.
// (x1, y1) is in the defined region, while (x2, y2) in the undefined one.
// The result is returned in
//--------------------------------------------------------------------------

static void
bisect( PLDEFINED_callback defined, PLINT niter,
        PLFLT x1, PLFLT y1, PLFLT x2, PLFLT y2, PLFLT* xb, PLFLT* yb )
{
    PLFLT xm;
    PLFLT ym;

    if ( niter == 0 )
    {
        *xb = x1;
        *yb = y1;
        return;
    }

    xm = ( x1 + x2 ) / 2.;
    ym = ( y1 + y2 ) / 2.;

    if ( defined( xm, ym ) )
        bisect( defined, niter - 1, xm, ym, x2, y2, xb, yb );
    else
        bisect( defined, niter - 1, x1, y1, xm, ym, xb, yb );
}

//--------------------------------------------------------------------------
// exfill()
//
// Fills a polygon from points in x[] and y[] with all points in
// undefined regions dropped and replaced by points at the bisected
// edge of the defined region.
// Note, undefined regions that are confined to the areas between polygon
// points are completely ignored.  Also, a range of undefined polygon points
// are simply replaced with a straight line with accurately bisected end
// points.  So this routine can produce problematic plotted results
// if the polygon is not a lot smaller than the typical resolution of
// the defined region.
//--------------------------------------------------------------------------

static void
exfill( PLFILL_callback fill, PLDEFINED_callback defined,
        int n, PLFLT_VECTOR x, PLFLT_VECTOR y )
{
    if ( n < 3 )
    {
        plabort( "exfill: Not enough points in object" );
        return;
    }

    if ( defined == NULL )

        ( *fill )( n, x, y );

    else
    {
        PLFLT *xx;
        PLFLT *yy;
        PLFLT xb, yb;
        PLINT count      = 0;
        PLINT im1        = n - 1;
        PLINT is_defined = defined( x[im1], y[im1] );
        PLINT i;

        // Slightly less than 2 n points are required for xx, yy, but
        // allocate room for 2 n to be safe.
        if ( ( xx = (PLFLT *) malloc( 2 * (size_t) n * sizeof ( PLFLT ) ) ) == NULL )
            plexit( "exfill: out of memory for xx" );
        if ( ( yy = (PLFLT *) malloc( 2 * (size_t) n * sizeof ( PLFLT ) ) ) == NULL )
            plexit( "exfill: out of memory for yy." );

        for ( i = 0; i < n; i++ )
        {
            // is_defined tells whether im1 point was in defined region.
            if ( defined( x[i], y[i] ) )
            {
                if ( !is_defined )
                {
                    // Cross from undefined (at im1) to defined region.
                    // Bisect for the first point inside the defined region
                    // and add it to xx, yy.
                    bisect( defined, NUMBER_BISECTIONS,
                        x[i], y[i], x[im1], y[im1], &xb, &yb );
                    xx[count]   = xb;
                    yy[count++] = yb;
                }
                // x[i], y[i] known to be in defined region so add this
                // point to xx, yy.
                xx[count]   = x[i];
                yy[count++] = y[i];
                is_defined  = 1;
            }
            else
            {
                if ( is_defined )
                {
                    // Cross from defined (at im1) to undefined region.
                    // Bisect for the last point in the defined region and
                    // add it to xx, yy.
                    bisect( defined, NUMBER_BISECTIONS,
                        x[im1], y[im1], x[i], y[i], &xb, &yb );
                    xx[count]   = xb;
                    yy[count++] = yb;
                    is_defined  = 0;
                }
            }
            im1 = i;
        }

        if ( count >= 3 )
            ( *fill )( count, (PLFLT_VECTOR) xx, (PLFLT_VECTOR) yy );

        free( xx );
        free( yy );
    }
}

//--------------------------------------------------------------------------
// big_recl()
//
// find a big rectangle for shading
//
// 2 goals: minimize calls to (*fill)()
//  keep ratio 1:3 for biggest rectangle
//
// only called by plshade()
//
// assumed that a 1 x 1 square already fits
//
// c[] = condition codes
// ny = c[1][0] == c[ny]  (you know what I mean)
//
// returns ix, iy = length of rectangle in grid units
//
// ix < dx - 1
// iy < dy - 1
//
// If iy == 1 -> ix = 1 (so that cond code can be set to skip)
//--------------------------------------------------------------------------

#define RATIO    3
#define COND( x, y )    cond_code[x * ny + y]

static void
big_recl( int *cond_code, register int ny, int dx, int dy,
          int *ix, int *iy )
{
    int          ok_x, ok_y, j;
    register int i, x, y;
    register int *cond;

    // ok_x = ok to expand in x direction
    // x = current number of points in x direction

    ok_x = ok_y = 1;
    x    = y = 2;

    while ( ok_x || ok_y )
    {
#ifdef RATIO
        if ( RATIO * x <= y || RATIO * y <= x )
            break;
#endif
        if ( ok_y )
        {
            // expand in vertical
            ok_y = 0;
            if ( y == dy )
                continue;
            cond = &COND( 0, y );
            for ( i = 0; i < x; i++ )
            {
                if ( *cond != OK )
                    break;
                cond += ny;
            }
            if ( i == x )
            {
                // row is ok
                y++;
                ok_y = 1;
            }
        }
        if ( ok_x )
        {
            if ( y == 2 )
                break;
            // expand in x direction
            ok_x = 0;
            if ( x == dx )
                continue;
            cond = &COND( x, 0 );
            for ( i = 0; i < y; i++ )
            {
                if ( *cond++ != OK )
                    break;
            }
            if ( i == y )
            {
                // column is OK
                x++;
                ok_x = 1;
            }
        }
    }

    // found the largest rectangle of 'ix' by 'iy'
    *ix = --x;
    *iy = --y;

    // set condition code to UNDEF in interior of rectangle

    for ( i = 1; i < x; i++ )
    {
        cond = &COND( i, 1 );
        for ( j = 1; j < y; j++ )
        {
            *cond++ = UNDEF;
        }
    }
}

//--------------------------------------------------------------------------
// draw_boundary()
//
// Draw boundaries of contour regions based on min_pts[], and max_pts[].
//--------------------------------------------------------------------------

static void
draw_boundary( PLINT slope, PLFLT *x, PLFLT *y )
{
    int i;

    if ( pen_col_min != 0 && pen_wd_min != 0 && min_points != 0 )
    {
        plcol0( pen_col_min );
        plwidth( pen_wd_min );
        if ( min_points == 4 && slope == 0 )
        {
            // swap points 1 and 3
            i          = min_pts[1];
            min_pts[1] = min_pts[3];
            min_pts[3] = i;
        }
        pljoin( x[min_pts[0]], y[min_pts[0]], x[min_pts[1]], y[min_pts[1]] );
        if ( min_points == 4 )
        {
            pljoin( x[min_pts[2]], y[min_pts[2]], x[min_pts[3]],
                y[min_pts[3]] );
        }
    }
    if ( pen_col_max != 0 && pen_wd_max != 0 && max_points != 0 )
    {
        plcol0( pen_col_max );
        plwidth( pen_wd_max );
        if ( max_points == 4 && slope == 0 )
        {
            // swap points 1 and 3
            i          = max_pts[1];
            max_pts[1] = max_pts[3];
            max_pts[3] = i;
        }
        pljoin( x[max_pts[0]], y[max_pts[0]], x[max_pts[1]], y[max_pts[1]] );
        if ( max_points == 4 )
        {
            pljoin( x[max_pts[2]], y[max_pts[2]], x[max_pts[3]],
                y[max_pts[3]] );
        }
    }
}

//--------------------------------------------------------------------------
//
// plctest( &(x[0][0]), PLFLT level)
// where x was defined as PLFLT x[4][4];
//
// determines if the contours associated with level have
// positive slope or negative slope in the box:
//
//  (2,3)   (3,3)
//
//  (2,2)   (3,2)
//
// this is heuristic and can be changed by the user
//
// return 1 if positive slope
//        0 if negative slope
//
// algorithmn:
//      1st test:
//	find length of contours assuming positive and negative slopes
//      if the length of the negative slope contours is much bigger
//	than the positive slope, then the slope is positive.
//      (and vice versa)
//      (this test tries to minimize the length of contours)
//
//      2nd test:
//      if abs((top-right corner) - (bottom left corner)) >
//	   abs((top-left corner) - (bottom right corner)) ) then
//		return negatiave slope.
//      (this test tries to keep the slope for different contour levels
//	the same)
//--------------------------------------------------------------------------

#define X( a, b )    ( x[a * 4 + b] )
#define POSITIVE_SLOPE    (PLINT) 1
#define NEGATIVE_SLOPE    (PLINT) 0
#define RATIO_SQ          6.0

static PLINT
plctest( PLFLT *x, PLFLT PL_UNUSED( level ) )
{
    int    i, j;
    double t[4], sorted[4], temp;

    sorted[0] = t[0] = X( 1, 1 );
    sorted[1] = t[1] = X( 2, 2 );
    sorted[2] = t[2] = X( 1, 2 );
    sorted[3] = t[3] = X( 2, 1 );

    for ( j = 1; j < 4; j++ )
    {
        temp = sorted[j];
        i    = j - 1;
        while ( i >= 0 && sorted[i] > temp )
        {
            sorted[i + 1] = sorted[i];
            i--;
        }
        sorted[i + 1] = temp;
    }
    // sorted[0] == min

    // find min contour
    temp = int_val * ceil( sorted[0] / int_val );
    if ( temp < sorted[1] )
    {
        // one contour line
        for ( i = 0; i < 4; i++ )
        {
            if ( t[i] < temp )
                return i / 2;
        }
    }

    // find max contour
    temp = int_val * floor( sorted[3] / int_val );
    if ( temp > sorted[2] )
    {
        // one contour line
        for ( i = 0; i < 4; i++ )
        {
            if ( t[i] > temp )
                return i / 2;
        }
    }
    // nothing better to do - be consistant
    return POSITIVE_SLOPE;
}

//--------------------------------------------------------------------------
// plctestez
//
// second routine - easier to use
// fills in x[4][4] and calls plctest
//
// test location a[ix][iy] (lower left corner)
//--------------------------------------------------------------------------

static PLINT
plctestez( PLFLT *a, PLINT nx, PLINT ny, PLINT ix,
           PLINT iy, PLFLT level )
{
    PLFLT x[4][4];
    int   i, j, ii, jj;

    for ( i = 0; i < 4; i++ )
    {
        ii = ix + i - 1;
        ii = MAX( 0, ii );
        ii = MIN( ii, nx - 1 );
        for ( j = 0; j < 4; j++ )
        {
            jj      = iy + j - 1;
            jj      = MAX( 0, jj );
            jj      = MIN( jj, ny - 1 );
            x[i][j] = a[ii * ny + jj];
        }
    }
    return plctest( &( x[0][0] ), level );
}
