/*ScianVisIso.c
  Routines for the isosurface visualization object
  Eric Pepke
  May 25, 1991
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianArrays.h"
#include "ScianWindows.h"
#include "ScianTextBoxes.h"
#include "ScianObjWindows.h"
#include "ScianIcons.h"
#include "ScianColors.h"
#include "ScianControls.h"
#include "ScianButtons.h"
#include "ScianTitleBoxes.h"
#include "ScianLists.h"
#include "ScianSpaces.h"
#include "ScianSliders.h"
#include "ScianIDs.h"
#include "ScianDatasets.h"
#include "ScianErrors.h"
#include "ScianVisObjects.h"
#include "ScianVisWindows.h"
#include "ScianStyle.h"
#include "ScianPictures.h"
#include "ScianDepend.h"
#include "ScianTimers.h"

float isoMaterial[30];
ObjPtr isoClass;
VertexPtr *iVertices;
VertexPtr *jVertices;
VertexPtr *kVertices;

/*Globals for IsoHex and CalcVertices*/
static int i, j, k, iDim, jDim, kDim;

int handleMissing;		/*Global flag for handling missing data*/

/*Specifier for a part of the hex, which can be linked into a list*/
typedef struct part 
    {
	struct part *next;	/*Next part in the list*/
	int coords[3];		/*Coordinates of the part.  
				  For a vertex, all three must be 0 or 1
				  For an edge, exactly one must be 2
				  For a face, two must be 2*/
    } Part, *PartPtr;

/*Parts for using*/
#define NPARTS	1580
Part parts[NPARTS];
PartPtr freePart, tempFree;

#define MAXNPOLYS 4

PartPtr presetPolys[256][MAXNPOLYS];

/*Macros for allocating parts*/
#define NEWPART	(freePart ? 						      \
		 (tempFree = freePart, freePart = freePart -> next, tempFree) \
		 : newp(Part))

#define FREEPART(p) p -> next = freePart; freePart = p;


#ifdef PROTO
Bool SectPart(PartPtr, PartPtr, PartPtr);
Bool IsoHex(real fieldHex[2][2][2], real surfVal, ObjPtr pic,
	    Bool lowInside);
#else
Bool SectPart();
Bool IsoHex();
#endif

/*Macro to insert a node after one node in the linked list*/
#define INSERTAFTER(list, node)						\
	{								\
	    node -> next = list -> next; 				\
	    list -> next = node;					\
	}

/*Macro to dispose of a node after the current node*/
#define DISPOSEAFTER(list)						\
	{								\
	    PartPtr next;						\
	    next = list -> next -> next;				\
	    FREEPART(list -> next);					\
	    list -> next = next;					\
	}

/*Values for vertexState*/
#define WITHIN	0		/*Within the isosurface*/
#define WITHOUT 1		/*Outside the isosurface*/
#define USED	2		/*Already used in the polygon*/

Bool EdgeOnFace(e, f)
PartPtr e, f;
/*Returns true if edge e is on face f*/
{
    register int k;
    for (k = 0; k < 3; ++k)
    {
	if (e -> coords[k] == 2 && f -> coords[k] != 2)
	{
	    return false;
	}
    }
    return true;
}

Bool SectPart(s1, s2, d)
PartPtr s1, s2, d;
/*Returns true if s1 intersects s2 and, if so, puts the intersection in d
  The intersection of two edges is a vertex; the intersection of two faces
  is an edge*/
{
    register int k;
  
    for (k = 0; k < 3; ++k)
    {
	if (s1 -> coords[k] == 2)
	{
	    d -> coords[k] = s2 -> coords[k];
	}
	else if (s2 -> coords[k] == 2)
	{
	    d -> coords[k] = s1 -> coords[k];
	}
	else if (s1 -> coords[k] != s2 -> coords[k])
	{
	    return false;
	}
	else
	{
	    d -> coords[k] = s1 -> coords[k];
	}
    }
    return true;
}

void UnionPart(s1, s2, d)
PartPtr s1, s2, d;
/*Puts in d the union of parts s1 and s2*/
{
    register int k;

    for (k = 0; k < 3; ++k)
    {
	if (s1 -> coords[k] == s2 -> coords[k])
	{
	    d -> coords[k] = s1 -> coords[k];
	}
	else
	{
	    d -> coords[k] = 2;
	}
    }
}

Bool ThirdEdge(s1, s2, d)
PartPtr s1, s2, d;
/*Given 2 edges, s1, and s2 which meet at a point, forms the third edge d*/
{
    register int k;

    for (k = 0; k < 3; ++k)
    {
	if (s1 -> coords[k] == 2)
	{
	    d -> coords[k] = s2 -> coords[k];
	}
	else if (s2 -> coords[k] == 2)
	{
	    d -> coords[k] = s1 -> coords[k];
	}
	else if (s1 -> coords[k] != s2 -> coords[k])
	{
	    return false;
	}
	else
	{
	    d -> coords[k] = 2;
	}
    }
    return true;
}

void Other2Edges(e, v, e1, e2)
PartPtr e, v, e1, e2;
/*Given an edge e and a vertex v on that edge, puts in e1 and e2 the other
  two edges which intersect that vertex*/
{
    register int index;				/*Index into which coordinate*/
    
    for (index = 0; index < 3; ++index)
    {
	if (e -> coords[index] != 2)
	{
	    e1 -> coords[0] = v -> coords[0];
	    e1 -> coords[1] = v -> coords[1];
	    e1 -> coords[2] = v -> coords[2];
	    e1 -> coords[index] = 2;
	    break;
	}
    }

    for (index = 2; index >= 0; --index)
    {
	if (e -> coords[index] != 2)
	{
	    e2 -> coords[0] = v -> coords[0];
	    e2 -> coords[1] = v -> coords[1];
	    e2 -> coords[2] = v -> coords[2];
	    e2 -> coords[index] = 2;
	    break;
	}
    }
}

#ifdef PROTO
Bool CacheIso(int initState[2][2][2], int whichPreset, Bool lowInside)
#else
Bool CacheIso(initState, whichPreset, lowInside)
int initState[2][2][2];
int whichPreset;
Bool lowInside;
#endif
/*Calculates a series of polygons that make an isosurface through fieldHex
  at surfVal.  Uses deltasHex as the deltas in the field to determine normal 
  information.  Uses coordHex to determine position of polygon points.
  Each time it makes a polygon, appends it to pic.  If lowInside, assumes
  that what is lower than surfVal is inside, otherwise what is higher*/
{
    int i, j, k;			/*Counters*/
    int vertexState[2][2][2];		/*State of each vertex in the hex*/
    int verticesLeft;			/*Number of within vertices left*/
    Bool retVal = true;			/*Return value*/
    int whichPolygon;			/*Which polygon of this polygon cache*/

    /*Figure out what the state of the vertices is*/
    verticesLeft = 0;
    for (i = 0; i < 2; ++i)
    {
	for (j = 0; j < 2; ++j)
	{
	    for(k = 0; k < 2; ++k)
	    {
		if (initState[i][j][k])
		{
		    vertexState[i][j][k] = lowInside ? WITHIN : WITHOUT;
		}
		else
		{
		    vertexState[i][j][k] = lowInside ? WITHOUT : WITHIN;
		}
		if (vertexState[i][j][k] == WITHIN)
		{
		    ++verticesLeft;
		}
	    }
	}
    }

    /*If all vertices are within, don't bother trying*/
    if (verticesLeft == 8)
    {
	return true;
    }

	whichPolygon = 0;
    /*Create isosurfaces until no more are needed*/
    while (verticesLeft)
    {
	int i0, j0, k0;			/*Index of starting vertex*/
	PartPtr polygon;		/*The polygon around the isosurface,
					  circular linked list*/
	register PartPtr oneEdge;	/*Temporary edge*/

	/*Pick an initial vertex around which to build the surface*/
	for (i = 0; i < 2; ++i)
	{
	    for (j = 0; j < 2; ++j)
	    {
		for (k = 0; k < 2; ++k)
		{
		    if (vertexState[i][j][k] == WITHIN)
		    {
			i0 = i;
			j0 = j;
			k0 = k;
		    }
		}
	    }
	}

	/*Now, i0, j0, and k0 are set to the initial vertex*/

	/*Create the first polygon and account for that vertex*/
	oneEdge = NEWPART;
	oneEdge -> next = oneEdge;
	oneEdge -> coords[0] = 2;
	oneEdge -> coords[1] = j0;
	oneEdge -> coords[2] = k0;
	polygon = oneEdge;

	/*Use checkerboard rule to determine initial order*/
	if (i0 ^ j0 ^ k0)
	{
	    oneEdge = NEWPART;
	    oneEdge -> coords[0] = i0;
	    oneEdge -> coords[1] = 2;
	    oneEdge -> coords[2] = k0;
	    INSERTAFTER(polygon, oneEdge);

	    oneEdge = NEWPART;
	    oneEdge -> coords[0] = i0;
	    oneEdge -> coords[1] = j0;
	    oneEdge -> coords[2] = 2;
	    INSERTAFTER(polygon, oneEdge);
	}
	else
	{
	    oneEdge = NEWPART;
	    oneEdge -> coords[0] = i0;
	    oneEdge -> coords[1] = j0;
	    oneEdge -> coords[2] = 2;
	    INSERTAFTER(polygon, oneEdge);

	    oneEdge = NEWPART;
	    oneEdge -> coords[0] = i0;
	    oneEdge -> coords[1] = 2;
	    oneEdge -> coords[2] = k0;
	    INSERTAFTER(polygon, oneEdge);
	}

	vertexState[i0][j0][k0] = USED;
	--verticesLeft;

	/*Now go through and expand the polygon*/
	for (;;)
	{
	    PartPtr runner;		/*Runner through the polygon*/

	    /*First see if there is any potential vertex, common to two 
	      existing adjacent edges which can combine them into one*/
	    runner = polygon;
	    do
	    {
		Part intersection;	/*Intersection between two edges*/
		if (SectPart(runner, runner -> next, &intersection))
		{
		    /*intersection contains a vertex where two edges meet*/
		    if (vertexState[intersection . coords[0]]
				   [intersection . coords[1]]
				   [intersection . coords[2]] == WITHIN)
		    {
			/*It's a valid candidate; gobble it up*/

			ThirdEdge(runner, runner -> next, runner);
			if (runner -> next == polygon)
			{
			    polygon = runner;
			}
			DISPOSEAFTER(runner);
			vertexState[intersection . coords[0]]
                                   [intersection . coords[1]]
                                   [intersection . coords[2]] = USED;
			--verticesLeft;
			goto tryAgain;
		    }
		}
		runner = runner -> next;
	    } while (runner != polygon);

	    /*Now see if an edge can be stretched over a neighboring vertex*/
	    runner = polygon;
	    do
	    {
		int index;		/*Index into which coord is free*/
		Part vertex;

		/*Start off with vertex = edge*/
		vertex . coords[0] = runner -> coords[0];
		vertex . coords[1] = runner -> coords[1];
		vertex . coords[2] = runner -> coords[2];
		
		/*Figure out which coordinate is free*/
		for (index = 0; index < 3; ++index)
		{
		    if (runner -> coords[index] == 2)
		    {
			break;
		    }
		}

		/*Now try both vertices that share that edge*/
		for (vertex . coords[index] = 0;
		     vertex . coords[index] < 2;
		     ++(vertex . coords[index]))
		{

		    if (vertexState[vertex . coords[0]]
				   [vertex . coords[1]]
				   [vertex . coords[2]] == WITHIN)
		    {
			/*Yes, it's good!  Snap it over it.*/
			Part lastFace;		/*Second face to climb over*/
			Part temp;		/*Temporary part*/
			Part edge1, edge2;	/*Candidate edges*/


			/*Determine the last face the new line will climb over
			  to determine the order of edges*/
			UnionPart(runner, runner -> next, &lastFace);

			/*Start with the edge that does not intersect lastFace*/
			Other2Edges(runner, &vertex, &edge1, &edge2);
			
			oneEdge = NEWPART;
			if (EdgeOnFace(&edge1, &lastFace))
			{
			    /*Start with edge2*/
			    runner -> coords[0] = edge2 . coords[0];
			    runner -> coords[1] = edge2 . coords[1];
			    runner -> coords[2] = edge2 . coords[2];

			    oneEdge -> coords[0] = edge1 . coords[0];
			    oneEdge -> coords[1] = edge1 . coords[1];
			    oneEdge -> coords[2] = edge1 . coords[2];
			}
			else
			{
			    /*Start with edge1*/
			    runner -> coords[0] = edge1 . coords[0];
			    runner -> coords[1] = edge1 . coords[1];
			    runner -> coords[2] = edge1 . coords[2];

			    oneEdge -> coords[0] = edge2 . coords[0];
			    oneEdge -> coords[1] = edge2 . coords[1];
			    oneEdge -> coords[2] = edge2 . coords[2];
			}

			INSERTAFTER(runner, oneEdge);

			vertexState[vertex . coords[0]]
                                   [vertex . coords[1]]
                                   [vertex . coords[2]] = USED;
			--verticesLeft;
			goto tryAgain;
		    }
		}

		runner = runner -> next;
	    } while (runner != polygon);

#if 0
	    /*Failed the two tests.  See if there are two identical intersections between two used
		vertices to slide*/
	    runner = polygon;
	    do
	    {
		int index;		/*Index into which coord is free*/
		Part vertex;

		/*Start off with vertex = edge*/
		vertex . coords[0] = runner -> coords[0];
		vertex . coords[1] = runner -> coords[1];
		vertex . coords[2] = runner -> coords[2];
		
		/*Figure out which coordinate is free*/
		for (index = 0; index < 3; ++index)
		{
		    if (runner -> coords[index] == 2)
		    {
			break;
		    }
		}

		/*Try both endpoints to see if they're both USED*/
		vertex . coords[index] = 0;
		if (vertexState[vertex . coords[0]]
                               [vertex . coords[1]]
                               [vertex . coords[2]] == USED)
		{
		    /*Looks good so far*/
		    vertex . coords[index] = 1;
		    if (vertexState[vertex . coords[0]]
                                   [vertex . coords[1]]
                                   [vertex . coords[2]] == USED)
		    {
			/*Excellent! Found one.  Now search for the other one, if any*/
			PartPtr other;
			other = runner -> next;
			do
			{
			    if (runner -> coords[0] == other -> coords[0] &&
				runner -> coords[1] == other -> coords[1] &&
				runner -> coords[2] == other -> coords[2])
			    {
				/*Hot diggity sclotos!*/
				break;
			    }
			    other = other -> next;
			} while (other != runner);
			if (other != runner)
			{
			    /*We found a mate!  Slide the two*/

			    runner -> coords[0] = other -> next -> coords[0];
			    runner -> coords[1] = other -> next -> coords[1];
			    runner -> coords[2] = other -> next -> coords[2];

			    other -> coords[0] = runner -> next -> coords[0];
			    other -> coords[1] = runner -> next -> coords[1];
			    other -> coords[2] = runner -> next -> coords[2];
			    break;
			}
		    }
		}

		runner = runner -> next;
	    } while (runner != polygon);
#endif
	    break;
	    
tryAgain:   ;
	}

	if (polygon)
	{
	    PartPtr curNode;		/*The current node in the polygon*/
	    PartPtr next;		/*The next node in the polygon*/
	    int whichVertex;		/*The current vertex*/
	    int k;

	    whichVertex = 0;

	    /*Test the polygon for sanity*/
	    curNode = polygon;
	    do
	    {
	    	register int n;		/*Dimension counters*/
		register int ic, jc, kc;

		int t1, t2;

		/*Determine along which side this intersection lies*/
		ic = curNode -> coords[0];
		jc = curNode -> coords[1];
		kc = curNode -> coords[2];
		if (ic == 2)
		{
		    /*i is free*/
		    t1 = initState[0][jc][kc];
		    t2 = initState[1][jc][kc];
		    if (t1 == t2)
		    {
			retVal = false;
			goto zapPolygon;
		    }
		}
		else if (jc == 2)
		{
		    /*j is free*/
		    t1 = initState[ic][0][kc];
		    t2 = initState[ic][1][kc];
		    if (t1 == t2)
		    {
			retVal = false;
			goto zapPolygon;
		    }
		}
		else if (kc == 2)
		{
		    /*k is free*/
		    t1 = initState[ic][jc][0];
		    t2 = initState[ic][jc][1];
		    if (t1 == t2)
		    {
			retVal = false;
			goto zapPolygon;
		    }
		}
		else
		{
		    ReportError("CacheIso", "Error, neither ic nor kc nor jc free");
		}

		curNode = curNode -> next;
	    } while (curNode != polygon);

	    if (whichPolygon >= MAXNPOLYS) ReportError("CacheIso", "Too many polys");
	    presetPolys[whichPreset][whichPolygon] = polygon;
	    ++whichPolygon;
	    continue;
zapPolygon:
	    /*Get rid of the polygon*/
	    curNode = polygon;
	    next = curNode -> next;
	    do
	    {
		curNode = next;
		next = curNode -> next;
		FREEPART(curNode);
	    } while (curNode != polygon);
	}
    }
    return retVal;
}

#if 0
#ifdef PROTO
Bool IsoHex(real fieldHex[2][2][2], real coordHex[2][2][2][3],
	     real deltasHex[2][2][2][3], real surfVal, PolysPtr polys,
	     Bool lowInside)
#else
Bool IsoHex(fieldHex, coordHex, deltasHex, surfVal, polys, lowInside)
real fieldHex[2][2][2];
real coordHex[2][2][2][3];
real deltasHex[2][2][2][3];
real surfVal;
PolysPtr pic;
Bool lowInside;
#endif
/*Calculates a series of polygons that make an isosurface through fieldHex
  at surfVal.  Uses deltasHex as the deltas in the field to determine normal 
  information.  Uses coordHex to determine position of polygon points.
  Each time it makes a polygon, appends it to pic.  If lowInside, assumes
  that what is lower than surfVal is inside, otherwise what is higher*/
{
    int i, j, k;			/*Counters*/
    int vertexState[2][2][2];		/*State of each vertex in the hex*/
    int verticesLeft;			/*Number of within vertices left*/
    Bool retVal = true;			/*Return value*/

    /*Figure out what the state of the vertices is*/
    verticesLeft = 0;
    for (i = 0; i < 2; ++i)
    {
	for (j = 0; j < 2; ++j)
	{
	    for(k = 0; k < 2; ++k)
	    {
		if (fieldHex[i][j][k] >= surfVal)
		{
		    vertexState[i][j][k] = lowInside ? WITHOUT : WITHIN;
		}
		else
		{
		    vertexState[i][j][k] = lowInside ? WITHIN : WITHOUT;
		}
		if (vertexState[i][j][k] == WITHIN)
		{
		    ++verticesLeft;
		}
	    }
	}
    }

    /*If all vertices are within, don't bother trying*/
    if (verticesLeft == 8)
    {
	return true;
    }

    /*Create isosurfaces until no more are needed*/
    while (verticesLeft)
    {
	int i0, j0, k0;			/*Index of starting vertex*/
	PartPtr polygon;		/*The polygon around the isosurface,
					  circular linked list*/
	register PartPtr oneEdge;	/*Temporary edge*/

	/*Pick an initial vertex around which to build the surface*/
	for (i = 0; i < 2; ++i)
	{
	    for (j = 0; j < 2; ++j)
	    {
		for (k = 0; k < 2; ++k)
		{
		    if (vertexState[i][j][k] == WITHIN)
		    {
			i0 = i;
			j0 = j;
			k0 = k;
		    }
		}
	    }
	}

	/*Now, i0, j0, and k0 are set to the initial vertex*/

	/*Create the first polygon and account for that vertex*/
	oneEdge = NEWPART;
	oneEdge -> next = oneEdge;
	oneEdge -> coords[0] = 2;
	oneEdge -> coords[1] = j0;
	oneEdge -> coords[2] = k0;
	polygon = oneEdge;

	oneEdge = NEWPART;
	oneEdge -> coords[0] = i0;
	oneEdge -> coords[1] = 2;
	oneEdge -> coords[2] = k0;
	INSERTAFTER(polygon, oneEdge);

	oneEdge = NEWPART;
	oneEdge -> coords[0] = i0;
	oneEdge -> coords[1] = j0;
	oneEdge -> coords[2] = 2;
	INSERTAFTER(polygon, oneEdge);
	vertexState[i0][j0][k0] = USED;
	--verticesLeft;

	/*Now go through and expand the polygon*/
	for (;;)
	{
	    PartPtr runner;		/*Runner through the polygon*/

	    /*First see if there is any potential vertex, common to two 
	      existing adjacent edges which can combine them into one*/
	    runner = polygon;
	    do
	    {
		Part intersection;	/*Intersection between two edges*/
		if (SectPart(runner, runner -> next, &intersection))
		{
		    /*intersection contains a vertex where two edges meet*/
		    if (vertexState[intersection . coords[0]]
				   [intersection . coords[1]]
				   [intersection . coords[2]] == WITHIN)
		    {
			/*It's a valid candidate; gobble it up*/

			ThirdEdge(runner, runner -> next, runner);
			if (runner -> next == polygon)
			{
			    polygon = runner;
			}
			DISPOSEAFTER(runner);
			vertexState[intersection . coords[0]]
                                   [intersection . coords[1]]
                                   [intersection . coords[2]] = USED;
			--verticesLeft;
			goto tryAgain;
		    }
		}
		runner = runner -> next;
	    } while (runner != polygon);

	    /*Now see if an edge can be stretched over a neighboring vertex*/
	    runner = polygon;
	    do
	    {
		int index;		/*Index into which coord is free*/
		Part vertex;

		/*Start off with vertex = edge*/
		vertex . coords[0] = runner -> coords[0];
		vertex . coords[1] = runner -> coords[1];
		vertex . coords[2] = runner -> coords[2];
		
		/*Figure out which coordinate is free*/
		for (index = 0; index < 3; ++index)
		{
		    if (runner -> coords[index] == 2)
		    {
			break;
		    }
		}

		/*Now try both vertices that share that edge*/
		for (vertex . coords[index] = 0;
		     vertex . coords[index] < 2;
		     ++(vertex . coords[index]))
		{

		    if (vertexState[vertex . coords[0]]
				   [vertex . coords[1]]
				   [vertex . coords[2]] == WITHIN)
		    {
			/*Yes, it's good!  Snap it over it.*/
			Part lastFace;		/*Second face to climb over*/
			Part temp;		/*Temporary part*/
			Part edge1, edge2;	/*Candidate edges*/


			/*Determine the last face the new line will climb over
			  to determine the order of edges*/
			UnionPart(runner, runner -> next, &lastFace);

			/*Start with the edge that does not intersect lastFace*/
			Other2Edges(runner, &vertex, &edge1, &edge2);
			
			oneEdge = NEWPART;
			if (EdgeOnFace(&edge1, &lastFace))
			{
			    /*Start with edge2*/
			    runner -> coords[0] = edge2 . coords[0];
			    runner -> coords[1] = edge2 . coords[1];
			    runner -> coords[2] = edge2 . coords[2];

			    oneEdge -> coords[0] = edge1 . coords[0];
			    oneEdge -> coords[1] = edge1 . coords[1];
			    oneEdge -> coords[2] = edge1 . coords[2];
			}
			else
			{
			    /*Start with edge1*/
			    runner -> coords[0] = edge1 . coords[0];
			    runner -> coords[1] = edge1 . coords[1];
			    runner -> coords[2] = edge1 . coords[2];

			    oneEdge -> coords[0] = edge2 . coords[0];
			    oneEdge -> coords[1] = edge2 . coords[1];
			    oneEdge -> coords[2] = edge2 . coords[2];
			}

			INSERTAFTER(runner, oneEdge);

			vertexState[vertex . coords[0]]
                                   [vertex . coords[1]]
                                   [vertex . coords[2]] = USED;
			--verticesLeft;
			goto tryAgain;
		    }
		}

		runner = runner -> next;
	    } while (runner != polygon);
	    break;
	    
tryAgain:   ;
	}

	if (polygon)
	{
	    PartPtr curNode;		/*The current node in the polygon*/
	    PartPtr next;		/*The next node in the polygon*/
	    Vertex vertices[10];	/*Vertices and normals*/
	    int whichVertex;		/*The current vertex*/
	    int k;

	    whichVertex = 0;

	    /*Emit the polygon*/
	    curNode = polygon;
	    do
	    {
	    	real location;		/*Location along edge from 0.0 to 1.0*/
		float absDelta;		/*Abs. val of delta*/
	    	real normal[3];		/*Vertex normal*/
	    	real vertex[3];		/*Vertex coordinates*/
	    	register int n;		/*Dimension counters*/
		register int ic, jc, kc;
		real rawNormal[3];

		/*Determine along which side this intersection lies*/
		ic = curNode -> coords[0];
		jc = curNode -> coords[1];
		kc = curNode -> coords[2];
		if (ic == 2)
		{
		    /*i is free*/
		    location = (surfVal - fieldHex[0][jc][kc]) / 
			(fieldHex[1][jc][kc] - fieldHex[0][jc][kc]);
		    if (location < 0.0) location = 1.0 - location;
		    if (location < 0.0 || location > 1.0)
		    {
			retVal = false;
			goto zapPolygon;
		    }
		    for (n = 0; n < 3; ++n)
		    {
	 		normal[n] = 
			    deltasHex[1][jc][kc][n] * location +
			    deltasHex[0][jc][kc][n] * (1.0 - location);
	 		vertex[n] = 
			    coordHex[1][jc][kc][n] * location +
			    coordHex[0][jc][kc][n] * (1.0 - location);
		    }
		}
		else if (jc == 2)
		{
		    /*j is free*/
		    location = (surfVal - fieldHex[ic][0][kc]) / 
			(fieldHex[ic][1][kc] - fieldHex[ic][0][kc]);
		    if (location < 0.0) location = 1.0 - location;
		    if (location < 0.0 || location > 1.0)
		    {
			retVal = false;
			goto zapPolygon;
		    }
		    for (n = 0; n < 3; ++n)
		    {
			normal[n] = 
			    deltasHex[ic][1][kc][n] * location +
			    deltasHex[ic][0][kc][n] * (1.0 - location);
			vertex[n] = 
			    coordHex[ic][1][kc][n] * location +
			    coordHex[ic][0][kc][n] * (1.0 - location);
		    }
		}
		else if (kc == 2)
		{
		    /*k is free*/
		    location = (surfVal - fieldHex[ic][jc][0]) / 
			(fieldHex[ic][jc][1] - fieldHex[ic][jc][0]);
		    if (location < 0.0) location = 1.0 - location;
		    if (location < 0.0 || location > 1.0)
		    {
			retVal = false;
			goto zapPolygon;
		    }
		    for (n = 0; n < 3; ++n)
		    {
			normal[n] = 
			    deltasHex[ic][jc][1][n] * location + 
			    deltasHex[ic][jc][0][n] * (1.0 - location);
			vertex[n] = 
			    coordHex[ic][jc][1][n] * location + 
			    coordHex[ic][jc][0][n] * (1.0 - location);
		    }
		}
		else
		{
		    ReportError("IsoHex", "Error, neither ic nor kc nor jc free");
		}

		/*Reverse direction of normal if not lowinside*/
		if (!lowInside)
		{
		    normal[0] = -normal[0];
		    normal[1] = -normal[1];
		    normal[2] = -normal[2];
		}

		/*Normalize normal*/
		absDelta = sqrt((double)
				 normal[0] * normal[0] +
				 normal[1] * normal[1] +
				 normal[2] * normal[2]);
for (n = 0; n < 3; ++n) rawNormal[n] = normal[n];
		normal[0] /= absDelta;
		normal[1] /= absDelta;
		normal[2] /= absDelta;
		/*Put it into normals and vertices*/
		for (n = 0; n < 3; ++n)
		{
		    vertices[whichVertex] . position[n] = vertex[n];
		    vertices[whichVertex] . normal[n] = normal[n];
		}
		vertices[whichVertex] . colorIndex = 0;
		++whichVertex;

		curNode = curNode -> next;
	    } while (curNode != polygon);

	    /*Now the polygon has been assembled; spit it out.*/
	    AppendPolyToPolys(polys, whichVertex, vertices);
zapPolygon:
	    /*Get rid of the polygon*/
	    curNode = polygon;
	    next = curNode -> next;
	    do
	    {
		curNode = next;
		next = curNode -> next;
		FREEPART(curNode);
	    } while (curNode != polygon);
	}
    }
    return retVal;
}
#endif

#if 0
#ifdef PROTO
Bool IsoHex(real fieldHex[2][2][2], real coordHex[2][2][2][3],
	     real deltasHex[2][2][2][3], real surfVal, PolysPtr polys,
	     Bool lowInside)
#else
Bool IsoHex(fieldHex, coordHex, deltasHex, surfVal, polys, lowInside)
real fieldHex[2][2][2];
real coordHex[2][2][2][3];
real deltasHex[2][2][2][3];
real surfVal;
PolysPtr pic;
Bool lowInside;
#endif
/*Calculates a series of polygons that make an isosurface through fieldHex
  at surfVal.  Uses deltasHex as the deltas in the field to determine normal 
  information.  Uses coordHex to determine position of polygon points.
  Each time it makes a polygon, appends it to pic.  If lowInside, assumes
  that what is lower than surfVal is inside, otherwise what is higher*/
{
    int t;				/*Counters*/
    real *fieldPtr;
    int whichCase;

    /*Calculate the case*/
    whichCase = 0;
    fieldPtr = &(fieldHex[0][0][0]);
    for (t = 0; t < 8; ++t)
    {
	whichCase = whichCase << 1;
	if (lowInside ? (*fieldPtr <= surfVal) : (*fieldPtr >= surfVal))
	{
	    whichCase += 1;
	}
	++fieldPtr;
    }
    
    for (t = 0; presetPolys[whichCase][t]; ++t)
    {
	PartPtr polygon;
	polygon = presetPolys[whichCase][t];
	if (polygon)
	{
	    PartPtr curNode;		/*The current node in the polygon*/
	    PartPtr next;		/*The next node in the polygon*/
	    Vertex vertices[10];	/*Vertices and normals*/
	    int whichVertex;		/*The current vertex*/

	    whichVertex = 0;

	    /*Emit the polygon*/
	    curNode = polygon;
	    do
	    {
	    	real location;		/*Location along edge from 0.0 to 1.0*/
		float absDelta;		/*Abs. val of delta*/
	    	real normal[3];		/*Vertex normal*/
	    	real vertex[3];		/*Vertex coordinates*/
	    	register int n;		/*Dimension counters*/
		register int ic, jc, kc;

		/*Determine along which side this intersection lies*/
		ic = curNode -> coords[0];
		jc = curNode -> coords[1];
		kc = curNode -> coords[2];
		if (ic == 2)
		{
		    /*i is free*/
		    location = (surfVal - fieldHex[0][jc][kc]) / 
			(fieldHex[1][jc][kc] - fieldHex[0][jc][kc]);
		    for (n = 0; n < 3; ++n)
		    {
	 		normal[n] = 
			    deltasHex[1][jc][kc][n] * location +
			    deltasHex[0][jc][kc][n] * (1.0 - location);
	 		vertex[n] = 
			    coordHex[1][jc][kc][n] * location +
			    coordHex[0][jc][kc][n] * (1.0 - location);
		    }
		}
		else if (jc == 2)
		{
		    /*j is free*/
		    location = (surfVal - fieldHex[ic][0][kc]) / 
			(fieldHex[ic][1][kc] - fieldHex[ic][0][kc]);
		    for (n = 0; n < 3; ++n)
		    {
			normal[n] = 
			    deltasHex[ic][1][kc][n] * location +
			    deltasHex[ic][0][kc][n] * (1.0 - location);
			vertex[n] = 
			    coordHex[ic][1][kc][n] * location +
			    coordHex[ic][0][kc][n] * (1.0 - location);
		    }
		}
		else if (kc == 2)
		{
		    /*k is free*/
		    location = (surfVal - fieldHex[ic][jc][0]) / 
			(fieldHex[ic][jc][1] - fieldHex[ic][jc][0]);
		    for (n = 0; n < 3; ++n)
		    {
			normal[n] = 
			    deltasHex[ic][jc][1][n] * location + 
			    deltasHex[ic][jc][0][n] * (1.0 - location);
			vertex[n] = 
			    coordHex[ic][jc][1][n] * location + 
			    coordHex[ic][jc][0][n] * (1.0 - location);
		    }
		}
		else
		{
		    ReportError("IsoHex", "Error, neither ic nor kc nor jc free");
		}

		/*Reverse direction of normal if not lowinside*/
		if (!lowInside)
		{
		    normal[0] = -normal[0];
		    normal[1] = -normal[1];
		    normal[2] = -normal[2];
		}

		/*Normalize normal*/
		absDelta = sqrt((double)
				 normal[0] * normal[0] +
				 normal[1] * normal[1] +
				 normal[2] * normal[2]);
		normal[0] /= absDelta;
		normal[1] /= absDelta;
		normal[2] /= absDelta;

		/*Put it into normals and vertices*/
		for (n = 0; n < 3; ++n)
		{
		    vertices[whichVertex] . position[n] = vertex[n];
		    vertices[whichVertex] . normal[n] = normal[n];
		}
		vertices[whichVertex] . colorIndex = 0;
		++whichVertex;

		curNode = curNode -> next;
	    } while (curNode != polygon);

	    /*Now the polygon has been assembled; spit it out.*/
	    AppendPolyToPolys(polys, whichVertex, vertices);
	}
    }
    return true;
}
#endif

#if 1
#ifdef PROTO
Bool IsoHex(real fieldHex[2][2][2], real surfVal, ObjPtr pic,
	    Bool lowInside)
#else
Bool IsoHex(fieldHex, surfVal, pic, lowInside)
real fieldHex[2][2][2];
real surfVal;
ObjPtr pic;
Bool lowInside;
#endif
/*Calculates a series of polygons that make an isosurface through fieldHex
  at surfVal.  Gets vertices from global arrays using global i, j, k, iDim, jDim, kDim.
  Each time it makes a polygon, appends it to pic.  If lowInside, assumes
  that what is lower than surfVal is inside, otherwise what is higher.  Uses
  information stored in global vertices to make hex*/
{
    int t;				/*Counters*/
    real *fieldPtr;
    int whichCase;

    /*Calculate the case*/
    whichCase = 0;
    fieldPtr = &(fieldHex[0][0][0]);
    for (t = 0; t < 8; ++t)
    {
	whichCase = whichCase << 1;

	if (lowInside ? (*fieldPtr <= surfVal) : (*fieldPtr >= surfVal))
	{
	    whichCase += 1;
	}
	++fieldPtr;
    }
    
    for (t = 0; presetPolys[whichCase][t]; ++t)
    {
	PartPtr polygon;
	polygon = presetPolys[whichCase][t];
	if (polygon)
	{
	    PartPtr curNode;		/*The current node in the polygon*/
	    PartPtr next;		/*The next node in the polygon*/
	    VertexPtr vertices[20];	/*The vertices into the polygon*/
	    int whichVertex;		/*Which vertex of the poly looking at*/

	    whichVertex = 0;

	    /*Emit the polygon*/
	    curNode = polygon;
	    do
	    {
		register int ic, jc, kc;
		register long offset;

		/*Determine along which side this intersection lies*/
		ic = curNode -> coords[0];
		jc = curNode -> coords[1];
		kc = curNode -> coords[2];
		if (ic == 2)
		{
		    /*i is free*/
		    offset = i +
			     (j + jc) * iDim +
			     (k + kc) * iDim * jDim;

		    vertices[whichVertex] = iVertices[offset];
		}
		else if (jc == 2)
		{
		    /*j is free*/
		    offset = (i + ic) +
			     j * iDim +
			     (k + kc) * iDim * jDim;

		    vertices[whichVertex] = jVertices[offset];
		}
		else if (kc == 2)
		{
		    /*k is free*/
		    offset = (i + ic) +
			     (j + jc) * iDim +
			     k * iDim * jDim;

		    vertices[whichVertex] = kVertices[offset];
		}
		else
		{
		    ReportError("IsoHex", "Error, neither ic nor kc nor jc free");
		    return false;
		}
		if (vertices[whichVertex] == 0 ||
		    vertices[whichVertex] == (VertexPtr) -1)
		{
		    ReportError("IsoHex", "Vertex error");
		    return false;
		}
		++whichVertex;

		curNode = curNode -> next;
	    } while (curNode != polygon);

	    /*Now the polygon has been assembled; spit it out.*/
	    TesselateSPolyToPicture(pic, whichVertex, vertices);
	}
    }
    return true;
}
#endif

/*Macro for making vertex only within CalcVertex*/
#define MAKEVERTEX(v, i1, j1, k1, i2, j2, k2)			\
    if (fieldHex[i1][j1][k1] == missingData ||			\
	fieldHex[i2][j2][k2] == missingData ||			\
	!BETWEENP(surfVal, fieldHex[i1][j1][k1],		\
		fieldHex[i2][j2][k2]))				\
    {								\
	v = 0;							\
    }								\
    else							\
    {	register real location;					\
	v = NewVertex(pic, 0);					\
	location = (surfVal - fieldHex[i1][j1][k1]) /		\
	       (fieldHex[i2][j2][k2] - fieldHex[i1][j1][k1]);	\
	for (n = 0; n < 3; ++n)					\
	{							\
	    v -> normal[n] =					\
		deltasHex[i2][j2][k2][n] * location +		\
		deltasHex[i1][j1][k1][n] * (1.0 - location);	\
	    v -> position[n] = 					\
		coordHex[i2][j2][k2][n] * location +		\
		coordHex[i1][j1][k1][n] * (1.0 - location);	\
	}							\
	NORMALIZE(v -> normal);					\
	if (!lowInside) {REVERSE(v -> normal);}			\
    }

#ifdef PROTO
void CalcVertices(real fieldHex[2][2][2], real coordHex[2][2][2][3],
	     real deltasHex[2][2][2][3], real surfVal, ObjPtr pic,
	     Bool lowInside)
#else
void CalcVertices(fieldHex, coordHex, deltasHex, surfVal, polys, lowInside)
real fieldHex[2][2][2];
real coordHex[2][2][2][3];
real deltasHex[2][2][2][3];
real surfVal;
ObjPtr pic;
Bool lowInside;
#endif
/*Calculates the vertices to contribute to an isosurface through fieldHex
  at surfVal.  Uses deltasHex as the deltas in the field to determine normal 
  information.  Uses coordHex to determine position of vertices.
  Each time it makes a vertex, appends it to pic and stuffs it in the 
  global [ijk]Vertices.  If lowInside, assumes
  that what is lower than surfVal is inside, otherwise what is higher
  Globals i, j, k are the indices.  Globals iDim, jDim, kDim are
  the dimensions.*/
{
    long whichVertex;		/*Index into vertices*/
    VertexPtr vertex;		/*Current vertex*/
    register int n;		/*Counter*/

    /*X11*/
    whichVertex = i + (j + 1) * iDim + (k + 1) * iDim * jDim;
    if (iVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 0, 1, 1, 1, 1, 1);
	iVertices[whichVertex] = vertex;
    }

    /*1X1*/
    whichVertex = (i + 1) + j * iDim + (k + 1) * iDim * jDim;
    if (jVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 1, 0, 1, 1, 1, 1);
	jVertices[whichVertex] = vertex;
    }

    /*11X*/
    whichVertex = (i + 1) + (j + 1) * iDim + k * iDim * jDim;
    if (kVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 1, 1, 0, 1, 1, 1);
	kVertices[whichVertex] = vertex;
    }

    /*Low i non-shared face*/

    /*01X*/
    whichVertex = i + (j + 1) * iDim + k * iDim * jDim;
    if (kVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 0, 1, 0, 0, 1, 1);
	kVertices[whichVertex] = vertex;
    }

    /*0X1*/
    whichVertex = i + j * iDim + (k + 1) * iDim * jDim;
    if (jVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 0, 0, 1, 0, 1, 1);
	jVertices[whichVertex] = vertex;
    }

    /*Low j non-shared face*/

    /*10X*/
    whichVertex = (i + 1) + j * iDim + k * iDim * jDim;
    if (kVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 1, 0, 0, 1, 0, 1);
	kVertices[whichVertex] = vertex;
    }

    /*X01*/
    whichVertex = i + j * iDim + (k + 1) * iDim * jDim;
    if (iVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 0, 0, 1, 1, 0, 1);
	iVertices[whichVertex] = vertex;
    }

    /*Low k non-shared face*/

    /*X10*/
    whichVertex = i + (j + 1) * iDim + k * iDim * jDim;
    if (iVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 0, 1, 0, 1, 1, 0);
	iVertices[whichVertex] = vertex;
    }

    /*1X0*/
    whichVertex = (i + 1) + j * iDim + k * iDim * jDim;
    if (jVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 1, 0, 0, 1, 1, 0);
	jVertices[whichVertex] = vertex;
    }

    /*Low ij shared edge*/

    /*00X*/
    whichVertex = i + j * iDim + k * iDim * jDim;
    if (kVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 0, 0, 0, 0, 0, 1);
	kVertices[whichVertex] = vertex;
    }

    /*Low jk shared edge*/

    /*X00*/
    if (iVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 0, 0, 0, 1, 0, 0);
	iVertices[whichVertex] = vertex;
    }

    /*Low ik shared edge*/

    /*0X0*/
    if (jVertices[whichVertex] == (VertexPtr) -1)
    {
	MAKEVERTEX(vertex, 0, 0, 0, 0, 1, 0);
	jVertices[whichVertex] = vertex;
    }
}

static ObjPtr ChangeIso(object)
ObjPtr object;
/*Changed value for a slider ISOVAL*/
{
    real newValue;
    ObjPtr val;
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeIso", object, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    val = GetVar(object, VALUE);
    if (val)
    {
	newValue = GetReal(val);
    }
    else
    {
	newValue = 30.0;
    }

    SetVar(repObj, ISOVAL, NewReal(newValue));
    ImInvalid(repObj);
    return ObjTrue;
}

static ObjPtr ChangeIsoMissing(object)
ObjPtr object;
/*Changed value for the isosurface handle missing data radio group*/
{
    int handleMissing;
    ObjPtr val;
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeIsoMissing", object, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    val = GetVar(object, VALUE);
    if (val)
    {
	handleMissing = GetInt(val);
    }
    else
    {
	handleMissing = 0;
    }

    SetVar(repObj, HANDLEMISSING, NewInt(handleMissing));

    ImInvalid(repObj);
    return ObjTrue;
}

static ObjPtr ChangeIsoEnclose(object)
ObjPtr object;
/*Changed value for the isosurface enclose radio group*/
{
    int enclose;
    ObjPtr val;
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeIsoEnclose", object, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    val = GetVar(object, VALUE);
    if (val)
    {
	enclose = GetInt(val);
    }
    else
    {
	enclose = 0;
    }

    SetVar(repObj, ENCLOSELOW, NewInt(enclose));

    ImInvalid(repObj);
    return ObjTrue;
}

static ObjPtr SetIsoMainDataset(visObj, dataSet)
ObjPtr visObj, dataSet;
/*Sets field to be the main field in visObj*/
{
   SetVar(visObj, MAINDATASET, dataSet);
   SetVar(visObj, ISOFIELD, dataSet);
   return ObjTrue;
}


static ObjPtr AddIsoControls(object, panelContents)
ObjPtr object, panelContents;
/*Adds controls appropriate to an isosurface to panelContents*/
{
    int left, right, top;
    real initValue;		/*The initial value of a slider*/
    ObjPtr var;			/*A variable of some sort*/
    ObjPtr slider;		/*A slider*/
    ObjPtr textBox;		/*A text box*/
    ObjPtr corral;		/*An icon corral*/
    int width;			/*Width of the area we can use*/
    ObjPtr name;		/*The name of the field*/
    ObjPtr icon;		/*An icon that represents the field*/
    ObjPtr isoField;		/*The field of the isosurface.*/
    ObjPtr defaultIcon;		/*The default icon of the field*/
    real db[2];			/*Bounds of the isosurface*/
    ObjPtr minMax;		/*Array containing min and max*/
    real bigStep, littleStep;	/*Big step and little step of slider*/
    real anchor;		/*Anchor for slider*/
    ObjPtr checkBox;		/*Temporary check box*/
    ObjPtr titleBox;		/*Temporary title box*/
    ObjPtr radioGroup;		/*Group of radio buttons*/
    ObjPtr mainDataset;		/*The main dataset of the field*/

    width = CWINWIDTH - 2 * CORRALBORDER - CWINCORRALWIDTH;
    left = MAJORBORDER;
    top = CWINHEIGHT - MAJORBORDER;

    /*Put in the isosurface corral at the left*/
    corral = NewIconCorral(NULLOBJ,
			   left, left + ONECORRALWIDTH,
			   top - ONECORRALHEIGHT,
			   top,
			   0);
    SetVar(corral, SINGLECORRAL, ObjTrue);
    SetVar(corral, TOPDOWN, ObjTrue);
    SetVar(corral, NAME, NewString("Source Field"));
    PrefixList(panelContents, corral);
    SetVar(corral, HELPSTRING,
	NewString("This corral shows the dataset that is being used to make \
the isosurface.  To replace it with another dataset, drag the icon of the other \
dataset into this corral."));
    SetVar(corral, PARENT, panelContents);
    SetVar(corral, REPOBJ, object);
    SetMethod(corral, DROPINCONTENTS, DropInMainDatasetCorral);

    /*Create the iso source text box*/
    textBox = NewTextBox(left, left + ONECORRALWIDTH, 
			 top - ONECORRALHEIGHT - TEXTBOXSEP - TEXTBOXHEIGHT,
			 top - ONECORRALHEIGHT - TEXTBOXSEP,
			 0, "Isosurface Field Text", "Source Field");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetTextAlign(textBox, CENTERALIGN);
    top = top - ONECORRALHEIGHT - TEXTBOXSEP - TEXTBOXHEIGHT - MAJORBORDER;
    right = left + ONECORRALWIDTH;

    /*Put in an icon that represents the field*/
    isoField = GetObjectVar("AddIsoControls", object, ISOFIELD);
    if (!isoField) return ObjFalse;
    MakeVar(isoField, MINMAX);
    minMax = GetVar(isoField, MINMAX);
    if (!minMax) return ObjFalse;

    Array2CArray(db, minMax);
    while (mainDataset = GetVar(isoField, MAINDATASET))
    {
	isoField = mainDataset;
    }

    name = GetVar(isoField, NAME);
    defaultIcon = GetVar(isoField, DEFAULTICON);
    if (defaultIcon)
    {
	icon = NewObject(defaultIcon, 0);
	SetVar(icon, NAME, name);
    }
    else
    {
	icon = NewIcon(0, 0, ICONQUESTION, GetString(name));
    }
    SetVar(icon, ICONLOC, NULLOBJ);
    DropIconInCorral(corral, icon);

    ChooseGoodSliderParams(&(db[0]), &(db[1]), &bigStep, &littleStep, &anchor);

    /*Create the isovalue slider*/
    slider = NewSlider(left + ONECORRALWIDTH + 2 * MAJORBORDER + SCALESLOP, 
			left + ONECORRALWIDTH + 2 * MAJORBORDER + SCALESLOP + SLIDERWIDTH - MINORBORDER,
			MAJORBORDER,
			CWINHEIGHT - MAJORBORDER, SCALE, "Isovalue");
    if (!slider)
    {
	return ObjFalse;
    }
    PrefixList(panelContents, slider);
    SetVar(slider, PARENT, panelContents);
    SetVar(slider, HELPSTRING,
	NewString("This slider controls the isosurface value shown by an \
isosurface visualization object.  Move the indicator to a new desired isosurface value and \
and a new isosurface will be calculated.  If you don't want the isosurface \
to be recalculated, you can turn off the icon representing the isosurface \
before you do this.  You can get multiple isosurfaces by duplicating the \
isosurface icon."));
    
    /*Put in a reference to this slider in the field*/
    SetVar(object, ISOSLIDER, slider);

    var = GetVar(object, ISOVAL);
    if (var && IsReal(var))
    {
	initValue = GetReal(var);
    }
    else
    {
	initValue = (db[0] + db[1]) * 0.5;
	if (initValue == 0.0) initValue += 5.0 * littleStep;
	SetVar(object, ISOVAL, NewReal(initValue));
    }
    SetSliderRange(slider, db[1], db[0], 0.0);
    SetSliderValue(slider, initValue);
    SetSliderScale(slider, bigStep, littleStep, anchor, "%g");
    SetVar(slider, REPOBJ, object);
    SetMethod(slider, CHANGEDVALUE, ChangeIso);
    SetTrackNot(slider, true);

    /*Create the value legend box*/
    textBox = NewTextBox(left, right,
			 top - TEXTBOXHEIGHT, top,
			 0, "Isovalue Text", "Isovalue:");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);

    top -= TEXTBOXHEIGHT + MINORBORDER;

    /*Create the value text box*/
    textBox = NewTextBox(left, 
			 right, 
			 top - EDITBOXHEIGHT,
			 top,
			 EDITABLE + WITH_PIT + ONE_LINE, "Isovalue Readout", "Isovalue");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetTextAlign(textBox, RIGHTALIGN);
    SliderReadout(slider, textBox);

    /*Create the missing data boxes*/
    right = width;
    left = right - ISOMISSLENGTH;
    top = MAJORBORDER + CHECKBOXHEIGHT * 4 + CHECKBOXSPACING * 3 + MINORBORDER;

    radioGroup = NewRadioButtonGroup("Missing Data");
    SetVar(radioGroup, HELPSTRING,
	NewString("These radio buttons give a variety of choices for how to handle missing data \
in the isosurface."));

    checkBox = NewRadioButton(left, right, top - CHECKBOXHEIGHT, top,
		"Leave open");
    SetVar(checkBox, HELPSTRING,
	NewString("This button causes isosurface sections to be left out when any of \
the corners of the cell contains missing data."));
    AddRadioButton(radioGroup, checkBox);
    top -= CHECKBOXHEIGHT + CHECKBOXSPACING;

    checkBox = NewRadioButton(left, right, top - CHECKBOXHEIGHT, top,
		"Treat as 0");
    SetVar(checkBox, HELPSTRING,
	NewString("This button causes missing data to be treated as 0 for the \
purposes of calculating isosurfaces."));
    AddRadioButton(radioGroup, checkBox);
    top -= CHECKBOXHEIGHT + CHECKBOXSPACING;

    checkBox = NewRadioButton(left, right, top - CHECKBOXHEIGHT, top,
		"Treat as infinity");
    SetVar(checkBox, HELPSTRING,
	NewString("This button causes missing data to be treated as infinity for the \
purposes of calculating isosurfaces."));
    AddRadioButton(radioGroup, checkBox);
    top -= CHECKBOXHEIGHT + CHECKBOXSPACING;

    checkBox = NewRadioButton(left, right, top - CHECKBOXHEIGHT, top,
		"Treat as -infinity");
    SetVar(checkBox, HELPSTRING,
	NewString("This button causes missing data to be treated as negative infinity for the \
purposes of calculating isosurfaces."));
    AddRadioButton(radioGroup, checkBox);
    top -= CHECKBOXHEIGHT;

    /*Title box around it*/
    titleBox = NewTitleBox(left - MINORBORDER, right,
		MAJORBORDER, MAJORBORDER + CHECKBOXHEIGHT * 4 + CHECKBOXSPACING * 3 + MINORBORDER * 2 + TITLEBOXTOP,
		"Treat Missing Data");
    PrefixList(panelContents, titleBox);
    SetVar(titleBox, PARENT, panelContents);

    /*Add the radio button group*/
    PrefixList(panelContents, radioGroup);
    SetVar(radioGroup, PARENT, panelContents);

    var = GetVar(object, HANDLEMISSING);
    if (var)
    {
	SetValue(radioGroup, var);
    }
    else
    {
	SetValue(radioGroup, NewInt(0));
    }

    SetVar(radioGroup, REPOBJ, object);
    SetMethod(radioGroup, CHANGEDVALUE, ChangeIsoMissing);

    /*Make radio buttons for enclosing*/
    top = CWINHEIGHT - MAJORBORDER - TITLEBOXTOP - MINORBORDER; 

    radioGroup = NewRadioButtonGroup("Enclose Data");
    SetVar(radioGroup, HELPSTRING,
	NewString("These radio buttons allow you to specify whether high or \
are to be enclosed within the isosurfaces.  This information is used to determine \
the direction of surface normals and is also used to improve the behavior of the \
isosurface routine in ambiguous cases.")); 

    checkBox = NewRadioButton(left, right, top - CHECKBOXHEIGHT, top,
		"High values");
    SetVar(checkBox, HELPSTRING,
	NewString("This button specifies that the shapes of the isosurface are \
trying to enclose values higher than the isosurface value."));
    AddRadioButton(radioGroup, checkBox);
    top -= CHECKBOXHEIGHT + CHECKBOXSPACING;

    checkBox = NewRadioButton(left, right, top - CHECKBOXHEIGHT, top,
		"Low values");
    SetVar(checkBox, HELPSTRING,
	NewString("This button specifies that the shapes of the isosurface are \
trying to enclose values lower than the isosurface value."));
    AddRadioButton(radioGroup, checkBox);
    top -= CHECKBOXHEIGHT + CHECKBOXSPACING;

    /*Title box around it*/
    titleBox = NewTitleBox(left - MINORBORDER, right,
		CWINHEIGHT - MAJORBORDER - CHECKBOXHEIGHT * 2 - CHECKBOXSPACING - MINORBORDER * 2 - TITLEBOXTOP,
		CWINHEIGHT - MAJORBORDER,
		"Enclose");
    PrefixList(panelContents, titleBox);
    SetVar(titleBox, PARENT, panelContents);

    /*Add the radio button group*/
    PrefixList(panelContents, radioGroup);
    SetVar(radioGroup, PARENT, panelContents);

    /*Set its value*/
    SetValue(radioGroup, GetPredicate(object, ENCLOSELOW) ? NewInt(1) : NewInt(0));
    SetVar(radioGroup, REPOBJ, object);
    SetMethod(radioGroup, CHANGEDVALUE, ChangeIsoEnclose);	

    return ObjTrue;
}

#define INDEXDATA(a, b, c) [(c) + (b) * kDim + (a) * kDim * jDim]
#define INDEXDELTA(a, b, c, d) [(d) + (c) * 3 + (b) * 3 * kDim + \
		(a) * 3 * kDim * jDim] 

real HandleMissing(input)
real input;
/*Handles missing data*/
{
    if (input == missingData)
    {
	switch (handleMissing)
	{
	    case 1:        /*Treat as 0*/
		return 0.0;
	    case 2:
		return PLUSINF;
	    case 3:
		return MINUSINF;
	}
    }
    return input;
}

ObjPtr MakeIsoSurface(object)
ObjPtr object;
/*Makes a SURFACE in isosurface vis object*/
{
    ObjPtr surface;
    ObjPtr dimsArray;
    ObjPtr deltas;
    real dims[3];		/*Dimensions of the data*/
    ObjPtr repObj;		/*Repobj of isosurface*/
    ObjPtr var;			/*Temp. place for variable*/
    real *deltasPtr;	
    ObjPtr isoValObj;
    real isoVal;
    real hex[2][2][2];
    real hexCoords[2][2][2][3];
    Bool encloseLow;
    double time;
    long nVertices, whichVertex;

    time = Clock();

    repObj = GetObjectVar("MakeIsoSurface", object, ISOFIELD);
    if (!repObj)
    {
	return ObjFalse;
    }

    dimsArray = GetDatasetFormDims(repObj);
    if (!dimsArray || !IsRealArray(dimsArray))
    {
	return ObjFalse;
    }
    Array2CArray(dims, dimsArray);
    iDim = dims[0];
    jDim = dims[1];
    kDim = dims[2];

    MakeVar(object, DELTAS);
    deltas = GetArrayVar("MakeIsoSurface", object, DELTAS);
    if (!deltas)
    {
	return ObjFalse;
    }

    /*Get the iso value.  Do this first because it might mess slider*/
    isoValObj = GetVar(object, ISOVAL);
    if (isoValObj)
    {
	isoVal = GetReal(isoValObj);
    }
    else
    {
	ObjPtr slider;
	real db[2], bigStep, littleStep, anchor;
	ObjPtr minMax;
	MakeVar(repObj, MINMAX);
	minMax = GetVar(repObj, MINMAX);
	Array2CArray(db, minMax);
    	ChooseGoodSliderParams(&(db[0]), &(db[1]), &bigStep, &littleStep, &anchor);
	isoVal = (db[0] + db[1]) * 0.5;
	if (isoVal == 0.0) isoVal += 5.0 * littleStep;
	SetVar(object, ISOVAL, NewReal(isoVal));

	/*See if there's a slider to update*/
	slider = GetVar(object, ISOSLIDER);
	if (slider)
	{
	    SetSliderValue(slider, isoVal);
	    SetSliderRange(slider, db[1], db[0], 0.0);
	    SetSliderScale(slider, bigStep, littleStep, anchor, "%g");
	}
    }

    var = GetVar(object, HANDLEMISSING);
    if (var)
    {
	handleMissing = GetInt(var);
    }
    else
    {
	handleMissing = 0;
    }

    encloseLow = GetPredicate(object, ENCLOSELOW);

    LongOperation();
    surface = NewPicture();
    if (!surface) return ObjFalse;

    SetVar(surface, REPOBJ, object);

    if (!SetCurField(FIELD1, repObj))
    {
	return ObjFalse;
    }

    if (!SetCurForm(FIELD2, repObj))
    {
	return ObjFalse;
    }

    /*Create vertex arrays*/
    nVertices = iDim * jDim * kDim;
    iVertices = (VertexPtr *) malloc(sizeof(VertexPtr) * nVertices);
    if (!iVertices)
    {
	return ObjFalse;
    }
    jVertices = (VertexPtr *) malloc(sizeof(VertexPtr) * nVertices);
    if (!jVertices)
    {
	free(iVertices);
	return ObjFalse;
    }
    kVertices = (VertexPtr *) malloc(sizeof(VertexPtr) * nVertices);
    if (!kVertices)
    {
	free(iVertices);
	free(jVertices);
	return ObjFalse;
    }

    /*Clear vertices*/
    for (whichVertex = 0; whichVertex < nVertices; ++whichVertex)
    {
	iVertices[whichVertex] = jVertices[whichVertex] = kVertices[whichVertex] = (VertexPtr) -1;
    }

    deltasPtr = ArrayMeat(deltas);
    for (i = 0; i < iDim - 1; ++i)
    {
	for (j = 0; j < jDim - 1; ++j)
	{
	    long di[3];
	    real square[2][2];
	    Bool noHex = false;
	    Bool lastNoHex = false;
	    di[0] = i;
	    di[1] = j;
	    di[2] = 0;

	    /*Get the hex and deal with missing data*/
	    StuffScalarIJSquare(square, FIELD1, di);
	    switch (handleMissing)
	    {
	        case 0:        /*Leave out*/
	            if (square[0][0] == missingData)
	            {
	                noHex = true;
	            }
	            if (square[0][1] == missingData)
	            {
	                noHex = true;
	            }
	            if (square[1][0] == missingData)
	            {
	                noHex = true;
	            }
	            if (square[1][1] == missingData)
	            {
	                noHex = true;
	            }
	            break;
	        case 1:        /*Treat as 0*/
	            if (square[0][0] == missingData)
	            {
	                square[0][0] = 0.0;
	            }
	            if (square[0][1] == missingData)
	            {
	                square[0][1] = 0.0;
	            }
	            if (square[1][0] == missingData)
	            {
	                square[1][0] = 0.0;
	            }
	            if (square[1][1] == missingData)
	            {
	                square[1][1] = 0.0;
	            }
	            break;
	        case 2:        /*Treat as infinite*/
	            if (square[0][0] == missingData)
	            {
	                square[0][0] = PLUSINF;
	            }
	            if (square[0][1] == missingData)
	            {
	                square[0][1] = PLUSINF;
	            }
	            if (square[1][0] == missingData)
	            {
	                square[1][0] = PLUSINF;
	            }
	            if (square[1][1] == missingData)
	            {
	                square[1][1] = PLUSINF;
	            }
	            break;
	        case 3:        /*Treat as -infinite*/
	            if (square[0][0] == missingData)
	            {
	                square[0][0] = MINUSINF;
	            }
	            if (square[0][1] == missingData)
	            {
	                square[0][1] = MINUSINF;
	            }
	            if (square[1][0] == missingData)
	            {
	                square[1][0] = MINUSINF;
	            }
	            if (square[1][1] == missingData)
	            {
	                square[1][1] = MINUSINF;
	            }
	            break;
	    }
	    hex[0][0][1] = square[0][0];
	    hex[0][1][1] = square[0][1];
	    hex[1][0][1] = square[1][0];
	    hex[1][1][1] = square[1][1];

	    /*Get the hex coordinates*/
	    StuffIJSquare(square, FIELD2, 0, di);
	    hexCoords[0][0][1][0] = square[0][0];
	    hexCoords[0][1][1][0] = square[0][1];
	    hexCoords[1][0][1][0] = square[1][0];
	    hexCoords[1][1][1][0] = square[1][1];

	    StuffIJSquare(square, FIELD2, 1, di);
	    hexCoords[0][0][1][1] = square[0][0];
	    hexCoords[0][1][1][1] = square[0][1];
	    hexCoords[1][0][1][1] = square[1][0];
	    hexCoords[1][1][1][1] = square[1][1];

	    StuffIJSquare(square, FIELD2, 2, di);
	    hexCoords[0][0][1][2] = square[0][0];
	    hexCoords[0][1][1][2] = square[0][1];
	    hexCoords[1][0][1][2] = square[1][0];
	    hexCoords[1][1][1][2] = square[1][1];

	    for (k = 0; k < kDim - 1; ++k)
	    {
	        int ip, jp, kp, n;

	        /*Shift the hex down and get next values*/
	        hex[0][0][0] = hex[0][0][1];
	        hex[0][1][0] = hex[0][1][1];
	        hex[1][0][0] = hex[1][0][1];
	        hex[1][1][0] = hex[1][1][1];

		di[0] = i;
		di[1] = j;
	        di[2] = k + 1;
	        StuffScalarIJSquare(square, FIELD1, di);

	        /*Deal with missing data*/
	        lastNoHex = noHex;
	        noHex = false;
	        switch (handleMissing)
	        {
	            case 0:        /*Leave out*/
	                if (square[0][0] == missingData)
	                {
	                    noHex = true;
	                }
	                if (square[0][1] == missingData)
	                {
	                    noHex = true;
	                }
	                if (square[1][0] == missingData)
	                {
	                    noHex = true;
	                }
	                if (square[1][1] == missingData)
	                {
	                    noHex = true;
	                }
	                break;
	            case 1:        /*Treat as 0*/
	                if (square[0][0] == missingData)
	                {
	                    square[0][0] = 0.0;
	                }
	                if (square[0][1] == missingData)
	                {
	                    square[0][1] = 0.0;
	                }
	                if (square[1][0] == missingData)
	                {
	                    square[1][0] = 0.0;
	                }
	                if (square[1][1] == missingData)
	                {
	                    square[1][1] = 0.0;
	                }
	                break;
	            case 2:        /*Treat as infinite*/
	                if (square[0][0] == missingData)
	                {
	                    square[0][0] = PLUSINF;
	                }
	                if (square[0][1] == missingData)
	                {
	                    square[0][1] = PLUSINF;
	                }
	                if (square[1][0] == missingData)
	                {
	                    square[1][0] = PLUSINF;
	                }
	                if (square[1][1] == missingData)
	                {
	                    square[1][1] = PLUSINF;
	                }
	                break;
	            case 3:        /*Treat as -infinite*/
	                if (square[0][0] == missingData)
	                {
	                    square[0][0] = MINUSINF;
	                }
	                if (square[0][1] == missingData)
	                {
	                    square[0][1] = MINUSINF;
	                }
	                if (square[1][0] == missingData)
	                {
	                    square[1][0] = MINUSINF;
	                }
	                if (square[1][1] == missingData)
	                {
	                    square[1][1] = MINUSINF;
	                }
	                break;
	        }
	        hex[0][0][1] = square[0][0];
	        hex[0][1][1] = square[0][1];
	        hex[1][0][1] = square[1][0];
	        hex[1][1][1] = square[1][1];

	        /*Shift the hex coordinates down and get next values*/
	        hexCoords[0][0][0][0] = hexCoords[0][0][1][0];
	        hexCoords[0][1][0][0] = hexCoords[0][1][1][0];
	        hexCoords[1][0][0][0] = hexCoords[1][0][1][0];
	        hexCoords[1][1][0][0] = hexCoords[1][1][1][0];

	        StuffIJSquare(square, FIELD2, 0, di);

	        hexCoords[0][0][1][0] = square[0][0];
	        hexCoords[0][1][1][0] = square[0][1];
	        hexCoords[1][0][1][0] = square[1][0];
	        hexCoords[1][1][1][0] = square[1][1];

	        hexCoords[0][0][0][1] = hexCoords[0][0][1][1];
	        hexCoords[0][1][0][1] = hexCoords[0][1][1][1];
	        hexCoords[1][0][0][1] = hexCoords[1][0][1][1];
	        hexCoords[1][1][0][1] = hexCoords[1][1][1][1];

	        StuffIJSquare(square, FIELD2, 1, di);

	        hexCoords[0][0][1][1] = square[0][0];
	        hexCoords[0][1][1][1] = square[0][1];
	        hexCoords[1][0][1][1] = square[1][0];
	        hexCoords[1][1][1][1] = square[1][1];

	        hexCoords[0][0][0][2] = hexCoords[0][0][1][2];
	        hexCoords[0][1][0][2] = hexCoords[0][1][1][2];
	        hexCoords[1][0][0][2] = hexCoords[1][0][1][2];
	        hexCoords[1][1][0][2] = hexCoords[1][1][1][2];

	        StuffIJSquare(square, FIELD2, 2, di);

	        hexCoords[0][0][1][2] = square[0][0];
	        hexCoords[0][1][1][2] = square[0][1];
	        hexCoords[1][0][1][2] = square[1][0];
	        hexCoords[1][1][1][2] = square[1][1];


		/*Calculate all the vertices we will need*/
		{
		    real *fieldPtr;
		    int whichCase = 0;
		    int t;

		    fieldPtr = &(hex[0][0][0]);
		    for (t = 0; t < 8; ++t)
		    {
			whichCase = whichCase << 1;
			if (encloseLow ? (*fieldPtr <= isoVal) : (*fieldPtr >= isoVal))
			{
			     whichCase += 1;
			}
			++fieldPtr;
		    }
		    if (whichCase > 0 && whichCase < 255)
		    {
	        if (!(noHex || lastNoHex))
	        {
		    real hexDeltas[2][2][2][3];
	        for (ip = 0; ip < 2; ++ip)
	        {
	            for (jp = 0; jp < 2; ++jp)
	            {
	                for (kp = 0; kp < 2; ++ kp)
	                {
			    /*See if deltas are missing*/
			    if (deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 0) == missingData ||
				deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 1) == missingData ||
				deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 2) == missingData)
			    {
				/*Must calculate them*/
				int nDeltas = 0;
				real cs, ds, dx, dy, dz, r2, t;

				deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 0) =
				deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 1) =
				deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 2) = 0.0;

				cs = HandleMissing(hex[ip][jp][kp]);
				if (cs != missingData)
				{
				    /*Not missing here, we have a chance*/
				    di[0] = i + ip;
				    di[1] = j + jp;
				    di[2] = k + kp;

				    /*i direction*/
				    if (di[0] < iDim - 1)
				    {
					++di[0];
					t = SelectFieldScalar(FIELD1, di);
					t = HandleMissing(t);
					if (t != missingData)
					{
					    ds = t - cs;
					    dx = SelectFieldComponent(FIELD2, 0, di) - 
						hexCoords[ip][jp][kp][0]; 
					    dy = SelectFieldComponent(FIELD2, 1, di) - 
						hexCoords[ip][jp][kp][1]; 
					    dz = SelectFieldComponent(FIELD2, 2, di) - 
						hexCoords[ip][jp][kp][2];
					    r2 = dx * dx + dy * dy + dz * dz;

					    if (r2 > 0.0)
					    {
						++nDeltas;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 0) +=
							dx * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 1) +=
							dy * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 2) +=
							dz * ds / r2;
					    }
					}
					--di[0];
				    }
				    
				    /*-i direction*/
				    if (di[0] > 0)
				    {
					--di[0];
					t = SelectFieldScalar(FIELD1, di);
					t = HandleMissing(t);
					if (t != missingData)
					{
					    ds = t - cs;
					    dx = SelectFieldComponent(FIELD2, 0, di) - 
						hexCoords[ip][jp][kp][0]; 
					    dy = SelectFieldComponent(FIELD2, 1, di) - 
						hexCoords[ip][jp][kp][1]; 
					    dz = SelectFieldComponent(FIELD2, 2, di) - 
						hexCoords[ip][jp][kp][2];
					    r2 = dx * dx + dy * dy + dz * dz;

					    if (r2 > 0.0)
					    {
						++nDeltas;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 0) +=
							dx * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 1) +=
							dy * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 2) +=
							dz * ds / r2;
					    }
					}
					++di[0];
				    }


				    /*j direction*/
				    if (di[1] < jDim - 1)
				    {
					++di[1];
					t = SelectFieldScalar(FIELD1, di);
					t = HandleMissing(t);
					if (t != missingData)
					{
					    ds = t - cs;
					    dx = SelectFieldComponent(FIELD2, 0, di) - 
						hexCoords[ip][jp][kp][0]; 
					    dy = SelectFieldComponent(FIELD2, 1, di) - 
						hexCoords[ip][jp][kp][1]; 
					    dz = SelectFieldComponent(FIELD2, 2, di) - 
						hexCoords[ip][jp][kp][2];
					    r2 = dx * dx + dy * dy + dz * dz;

					    if (r2 > 0.0)
					    {
						++nDeltas;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 0) +=
							dx * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 1) +=
							dy * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 2) +=
							dz * ds / r2;
					    }
					}
					--di[1];
				    }
				    
				    /*-j direction*/
				    if (di[1] > 0)
				    {
					--di[1];
					t = SelectFieldScalar(FIELD1, di);
					t = HandleMissing(t);
					if (t != missingData)
					{
					    ds = t - cs;
					    dx = SelectFieldComponent(FIELD2, 0, di) - 
						hexCoords[ip][jp][kp][0]; 
					    dy = SelectFieldComponent(FIELD2, 1, di) - 
						hexCoords[ip][jp][kp][1]; 
					    dz = SelectFieldComponent(FIELD2, 2, di) - 
						hexCoords[ip][jp][kp][2];
					    r2 = dx * dx + dy * dy + dz * dz;

					    if (r2 > 0.0)
					    {
						++nDeltas;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 0) +=
							dx * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 1) +=
							dy * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 2) +=
							dz * ds / r2;
					    }
					}
					++di[1];
				    }

				    /*k direction*/
				    if (di[2] < kDim - 1)
				    {
					++di[2];
					t = SelectFieldScalar(FIELD1, di);
					t = HandleMissing(t);
					if (t != missingData)
					{
					    ds = t - cs;
					    dx = SelectFieldComponent(FIELD2, 0, di) - 
						hexCoords[ip][jp][kp][0]; 
					    dy = SelectFieldComponent(FIELD2, 1, di) - 
						hexCoords[ip][jp][kp][1]; 
					    dz = SelectFieldComponent(FIELD2, 2, di) - 
						hexCoords[ip][jp][kp][2];
					    r2 = dx * dx + dy * dy + dz * dz;

					    if (r2 > 0.0)
					    {
						++nDeltas;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 0) +=
							dx * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 1) +=
							dy * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 2) +=
							dz * ds / r2;
					    }
					}
					--di[2];
				    }
				    
				    /*-k direction*/
				    if (di[2] > 0)
				    {
					--di[2];
					t = SelectFieldScalar(FIELD1, di);
					t = HandleMissing(t);
					if (t != missingData)
					{
					    ds = t - cs;
					    dx = SelectFieldComponent(FIELD2, 0, di) - 
						hexCoords[ip][jp][kp][0]; 
					    dy = SelectFieldComponent(FIELD2, 1, di) - 
						hexCoords[ip][jp][kp][1]; 
					    dz = SelectFieldComponent(FIELD2, 2, di) - 
						hexCoords[ip][jp][kp][2];
					    r2 = dx * dx + dy * dy + dz * dz;

					    if (r2 > 0.0)
					    {
						++nDeltas;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 0) +=
							dx * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 1) +=
							dy * ds / r2;
						deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 2) +=
							dz * ds / r2;
					    }
					}
					++di[2];
				    }

				    /*Now average them*/
				    if (nDeltas)
				    {
					deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 0) /=
						(real) nDeltas;
					deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 1) /=
						(real) nDeltas;
					deltasPtr INDEXDELTA(i+ip, j+jp, k+kp, 2) /=
						(real) nDeltas;
				    }
				}
			    }
	                    for (n = 0; n < 3; ++n)
	                    {
	                        hexDeltas[ip][jp][kp][n]=
	                            deltasPtr INDEXDELTA(
	                             i+ip, j+jp, k+kp, n);
	                    }
	                }
	            }
	        }
			CalcVertices(hex, hexCoords, hexDeltas,
				isoVal, surface, encloseLow);

	            if (!IsoHex(hex, isoVal, surface, encloseLow))
	            {
	                /*Kludge--reverse sense of comparison*/
	                for (ip = 0; ip < 2; ++ip)
	                {
	                    for (jp = 0; jp < 2; ++jp)
	                    {
	                        for (kp = 0; kp < 2; ++ kp)
	                        {
	                            for (n = 0; n < 3; ++n)
	                            {
	                                hexDeltas[ip][jp][kp][n]=
	                                -hexDeltas[ip][jp][kp][n];
	                            }
	                        }
	                    }
	                }
			IsoHex(hex, isoVal, surface, encloseLow ? false : true);
	            }
	        }
		    }
		}
	    }
	}
    }

    free(iVertices);
    free(jVertices);
    free(kVertices);

/*    CalcPictureNormals(surface);*/
    SetVar(object, SURFACE, surface);
    MakeVar(object, CPALETTE);
    SetVar(surface, CPALETTE, GetVar(object, CPALETTE));

    return ObjTrue;
}

static ObjPtr MakeIsoDeltas(object)
ObjPtr object;
/*Makes the deltas within object*/
{
    ObjPtr deltas;
    real dims[3];
    int iDims[3];
    ObjPtr formObj;
    ObjPtr dimsArray;
    ObjPtr repObj;
    real *deltasPtr;
    int i, j, k;

    repObj = GetObjectVar("MakeIsoDeltas", object, ISOFIELD);
    if (!repObj)
    {
	return ObjFalse;
    }

    formObj = GetObjectVar("MakeIsoDeltas", repObj, DATAFORM);
    if (!formObj)
    {
	return ObjFalse;
    }

    dimsArray = GetFixedArrayVar("MakeIsoDeltas", formObj, DIMENSIONS, 1, 3L);
    if (!dimsArray || !IsRealArray(dimsArray))
    {
	return ObjFalse;
    }
    Array2CArray(dims, dimsArray);
    iDims[0] = dims[0];
    iDims[1] = dims[1];
    iDims[2] = dims[2];

    deltas = NewRealArray(4, (long) iDims[0], (long) iDims[1], (long) iDims[2], (long) 3L);
    SetVar(object, DELTAS, deltas);
    deltasPtr = ArrayMeat(deltas);
    
    /*Make it all missing*/
    for (j = 0; j < iDims[1]; ++j)
    {
	for (k = 0; k < iDims[2]; ++k)
	{
	    for (i = 0; i < iDims[0]; ++i)
	    {
		deltasPtr INDEXDELTA(i, j, k, 0) = missingData;
		deltasPtr INDEXDELTA(i, j, k, 1) = missingData;
		deltasPtr INDEXDELTA(i, j, k, 2) = missingData;
	    }
	}
    }
    return ObjTrue;
}

void InitIsosurfaces()
{
    ObjPtr icon;
    int i, j, k, n;
    int initState[2][2][2];
    int *statePtr;
    int stateFlags, tempFlags;

    /*Initialize the free space*/
    parts[0] . next = 0;
    for (k = 1; k < NPARTS; ++k)
    {
	parts[k] . next = &(parts[k - 1]);
    }
    freePart = &(parts[NPARTS - 1]);

    /*Zero the polygons*/
    for (i = 0; i < 256; ++i)
    {
	for (j = 0; j < MAXNPOLYS; ++j)
	{
	    presetPolys[i][j] = 0;
	}
    }

    /*Fill the polygons*/
    for (stateFlags = 0; stateFlags < 256; ++stateFlags)
    {
	/*Fill initState based on stateFlag*/
	tempFlags = stateFlags;
	statePtr = &(initState[0][0][0]);
	for (k = 0; k < 8; ++k)
	{
	    *statePtr++ = (tempFlags & 128) ? 1 : 0;
	    tempFlags = tempFlags << 1;
	}

	if (!CacheIso(initState, stateFlags, false))
	{
	    CacheIso(initState, stateFlags, true);
	}
    }


    /*Class for an isosurface*/
    isoClass = NewObject(visSurface, 0);
    AddToReferenceList(isoClass);
    SetVar(isoClass, NAME, NewString("Isosurface"));
    SetMethod(isoClass, ADDCONTROLS, AddIsoControls);
    SetVar(isoClass, DEFAULTICON, icon = NewObject(visIcon, 0));
    SetVar(icon, WHICHICON, NewInt(ICONISOSURFACE));
    SetVar(icon, NAME, NewString("Isosurface"));
    SetVar(icon, HELPSTRING,
	NewString("This icon represents an isosurface.  \
The isosurface object generates surfaces of constant value through 3-D \
scalar datasets defined over structured grids.  Some day it will work with \
nonstructured grids, as well."));
    icon = NewIcon(0, 0, ICONISOSURFACE, "Isosurface");
    SetVar(icon, HELPSTRING,
	NewString("Select this icon to show controls for the isosurface, such \
as the isosurface level and the treatment of missing data."));
    SetVar(isoClass, CONTROLICON, icon);

    SetMethod(isoClass, SETMAINDATASET, SetIsoMainDataset);
    SetVar(isoClass, EXTENSION, NewString("iso"));
    SetVar(isoClass, HANDLEMISSING, NewInt(0));
    DeclareDependency(isoClass, SURFACE, ISOVAL);
    DeclareDependency(isoClass, SURFACE, HANDLEMISSING);
    DeclareDependency(isoClass, SURFACE, ENCLOSELOW);
    DeclareIndirectDependency(isoClass, SURFACE, ISOFIELD, DATA);
    DeclareIndirectDependency(isoClass, SURFACE, ISOFIELD, CURDATA);
    DeclareDependency(isoClass, SURFACE, ISOFIELD);
    SetMethod(isoClass, SURFACE, MakeIsoSurface);

    DeclareIndirectDependency(isoClass, DELTAS, ISOFIELD, DATA);
    DeclareIndirectDependency(isoClass, DELTAS, ISOFIELD, CURDATA);
    SetMethod(isoClass, DELTAS, MakeIsoDeltas);

    DefineVisMapping(DS_HASFORM | DS_HASFIELD, 3, 3, -1, isoClass);
    DefineVisMapping(DS_HASFORM | DS_HASFIELD | DS_VECTOR, 3, 3, -1, isoClass);
}

void KillIsosurfaces()
{
    DeleteThing(isoClass);
}
