/*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"
#include "ScianScales.h"
#include "ScianTemplates.h"
#include "ScianTemplateHelper.h"
#include "ScianEvents.h"
#include "ScianScripts.h"

ObjPtr isoClass;

/*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, storing it in whichPreset.  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] = 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);
	}
	else
	{
	    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);

#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;
}

#ifdef PROTO
Bool NewIsoHex(real *bottomField, real *topField,
	VertexPtr *bottomVertices, VertexPtr *topVertices,
	long index[], long dims[],
	real surfVal, ObjPtr pic,
	Bool lowInside)
#else
Bool NewIsoHex(bottomField, topField,
	bottomVertices, topVertices,
	index, dims,
	surfVal, pic,
	lowInside)
real *bottomField; real *topField;
VertexPtr *bottomVertices; VertexPtr *topVertices;
long index[]; long dims[];
real surfVal; ObjPtr pic;
Bool lowInside;
#endif
/*Calculates a series of polygons that make an isosurface.*/
{
    int i, j, t;				/*Counters*/
    real sample;
    int whichCase;
    register long offset;

    /*Calculate the case*/
    whichCase = 0;
    for (i = 0; i < 2; ++i)
    {
	for (j = 0; j < 2; ++j)
	{
	    offset = (index[0] + i) * dims[1] + (index[1] + j);

	    whichCase = whichCase << 1;

	    sample = bottomField[offset];
	    if (sample == missingData) return true;
	    if (lowInside ? (sample <= surfVal) : (sample >= surfVal))
	    {
		whichCase += 1;
	    }

	    whichCase = whichCase << 1;

	    sample = topField[offset];
	    if (sample == missingData) return true;
	    if (lowInside ? (sample <= surfVal) : (sample >= surfVal))
	    {
		whichCase += 1;
	    }
	}
    }

    i = index[0];
    j = index[1];
    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;

		/*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 * dims[1] + (j + jc)) * 3;
		}
		else if (jc == 2)
		{
		    /*j is Free*/
		    offset = ((i + ic) * dims[1] + j) * 3 + 1;
		}
		else if (kc == 2)
		{
		    /*k is Free*/
		    offset = ((i + ic) * dims[1] + (j + jc)) * 3 + 2;
		}
		else
		{
		    ReportError("NewIsoHex", "Error, neither ic nor kc nor jc Free");
		    return false;
		}

		vertices[whichVertex] = (kc == 1) ? topVertices[offset] : bottomVertices[offset];

		if (vertices[whichVertex] == 0)
		{
		    ReportError("NewIsoHex", "Vertex error");
		    return false;
		}
		++whichVertex;

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

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

#define CALCDELTA(field, hc, pic, mic, pjc, mjc, pkc, mkc)

#undef	NEXTFIELD
#undef	PREVFIELD
#undef	PLUSIC
#undef	MINUSIC
#undef	PLUSJC
#undef	MINUSJC
#undef	PLUSKC
#undef	MINUSKC


#ifdef PROTO
static void StuffIJKVertices(VertexPtr *elements, real *field, real *nextField, real deltas[], int fieldNum, int coord, long dims[3], long kVal, ObjPtr pic, real isoVal, Bool encloseLow)
#else
static void StuffIJKVertices(elements, field, nextField, deltas, fieldNum, coord, dims, kVal, pic, isoVal, encloseLow)
VertexPtr *elements;
real *field;
int fieldNum;
real *nextField;
real deltas[];
int coord;
long dims[3];
long kVal;
ObjPtr pic;
real isoVal;
Bool encloseLow;
#endif
/*Makes a group of IJK vertices for field at k = kVal, sticking them in pic
  Coord is the coordinates.  Stuffs the results into array.  If deltas, uses
  deltas as a holding place for the deltas to make normals*/
{
    long index[3];
    register long nElements;
    register long i, j;
    register long offset;
    register real curVal, nextVal;
    register real r;

    /*Empty the array*/
    nElements = dims[0] * dims[1] * 3;
    for (offset = 0; offset < nElements; ++offset)
    {
	elements[offset] = 0;
    }

    /*Make I vertices*/
    for (index[1] = 0; index[1] < dims[1]; ++index[1])
    {
	index[0] = 0;
	index[2] = kVal;

	offset = index[1];

	curVal = field[offset];

	for (index[0] = 1; index[0] < dims[0]; ++index[0])
	{
	    nextVal = field[offset + dims[1]];

	    if (curVal != missingData && nextVal != missingData)
	    {
		/*Maybe make some sort of a vertex*/

		if (BETWEENP(isoVal, curVal, nextVal))
		{
		    /*Yes!  Vertex here!*/
		    register VertexPtr v;
		    register real location, location1;
		    real curCoord[3], nextCoord[3];

		    v = NewVertex(pic, 0);
		    location = (isoVal - curVal) / (nextVal - curVal);
		    location1 = 1.0 - location;

		    /*Get coordinates*/
		    --index[0];
		    curCoord[0] = SelectFieldComponent(coord, 0, index);
		    curCoord[1] = SelectFieldComponent(coord, 1, index);
		    curCoord[2] = SelectFieldComponent(coord, 2, index);
		    ++index[0];
		    nextCoord[0] = SelectFieldComponent(coord, 0, index);
		    nextCoord[1] = SelectFieldComponent(coord, 1, index);
		    nextCoord[2] = SelectFieldComponent(coord, 2, index);

		    /*Set position*/
		    v -> position[0] = nextCoord[0] * location +
				       curCoord[0] * location1;
		    v -> position[1] = nextCoord[1] * location +
				       curCoord[1] * location1;
		    v -> position[2] = nextCoord[2] * location +
				       curCoord[2] * location1;

		    if (deltas)
		    {
			/*Calculate normal from delta*/
			--index[0];
#define FIELDHERE	field
#define NEXTFIELD	nextField
#define HEREC		curCoord
#define HEREV		curVal
#define PLUSIC		nextCoord
#include "ScianIsoCalcDelta.h"
#undef FIELDHERE
#undef NEXTFIELD
#undef HEREC
#undef HEREV
#undef PLUSIC
			++index[0];

#define FIELDHERE	field
#define NEXTFIELD	nextField
#define HEREC		nextCoord
#define HEREV		nextVal
#define MINUSIC		curCoord
#include "ScianIsoCalcDelta.h"
#undef FIELDHERE
#undef NEXTFIELD
#undef HEREC
#undef HEREV
#undef MINUSIC

			/*Now that we have two deltas, make the normal*/
			offset += dims[1];

			v -> normal[0] = location * deltas[offset * 3];
			v -> normal[1] = location * deltas[offset * 3 + 1];
			v -> normal[2] = location * deltas[offset * 3 + 2];

			offset -= dims[1];

			v -> normal[0] += location1 * deltas[offset * 3];
			v -> normal[1] += location1 * deltas[offset * 3 + 1];
			v -> normal[2] += location1 * deltas[offset * 3 + 2];

			r = sqrt(SQUARE(v -> normal[0]) +
				 SQUARE(v -> normal[1]) +
				 SQUARE(v -> normal[2]));
			if (r > 0.0)
			{
			    r = 1.0 / r;
			    v -> normal[0] *= r;
			    v -> normal[1] *= r;
			    v -> normal[2] *= r;

			    if (!encloseLow)
			    {
				v -> normal[0] = -(v -> normal[0]);
				v -> normal[1] = -(v -> normal[1]);
				v -> normal[2] = -(v -> normal[2]);
			    }
			}
			else
			{
			    /*Just give it plus z normal*/

			    v -> normal[0] = 0.0;
			    v -> normal[1] = 0.0;
			    v -> normal[2] = 1.0;
			}
		    }
		    else
		    {
			/*Just give it plus z normal*/

			v -> normal[0] = 0.0;
			v -> normal[1] = 0.0;
			v -> normal[2] = 1.0;
		    }

		    elements[offset * 3] = v;
		}
	    }
	    curVal = nextVal;
	    offset += dims[1];
	}
    }

    /*Make J vertices*/
    for (index[0] = 0; index[0] < dims[0]; ++index[0])
    {
	index[1] = 0;
	index[2] = kVal;

	offset = index[0] * dims[1];

	curVal = field[offset];

	for (index[1] = 1; index[1] < dims[1]; ++index[1])
	{
	    nextVal = field[offset + 1];

	    if (curVal != missingData && nextVal != missingData)
	    {
		/*Maybe make some sort of a vertex*/

		if (BETWEENP(isoVal, curVal, nextVal))
		{
		    /*Yes!  Vertex here!*/
		    register VertexPtr v;
		    register real location, location1;
		    real curCoord[3], nextCoord[3];

		    v = NewVertex(pic, 0);
		    location = (isoVal - curVal) / (nextVal - curVal);
		    location1 = 1.0 - location;

		    /*Get coordinates*/
		    --index[1];
		    curCoord[0] = SelectFieldComponent(coord, 0, index);
		    curCoord[1] = SelectFieldComponent(coord, 1, index);
		    curCoord[2] = SelectFieldComponent(coord, 2, index);
		    ++index[1];
		    nextCoord[0] = SelectFieldComponent(coord, 0, index);
		    nextCoord[1] = SelectFieldComponent(coord, 1, index);
		    nextCoord[2] = SelectFieldComponent(coord, 2, index);

		    /*Set position*/
		    v -> position[0] = nextCoord[0] * location +
				       curCoord[0] * location1;
		    v -> position[1] = nextCoord[1] * location +
				       curCoord[1] * location1;
		    v -> position[2] = nextCoord[2] * location +
				       curCoord[2] * location1;

		    if (deltas)
		    {
			/*Calculate normal from delta*/
			--index[1];
#define FIELDHERE	field
#define NEXTFIELD	nextField
#define HEREC		curCoord
#define HEREV		curVal
#define PLUSJC		nextCoord
#include "ScianIsoCalcDelta.h"
#undef FIELDHERE
#undef NEXTFIELD
#undef HEREC
#undef HEREV
#undef PLUSJC
			++index[1];

#define FIELDHERE	field
#define NEXTFIELD	nextField
#define HEREC		nextCoord
#define HEREV		nextVal
#define MINUSJC		curCoord
#include "ScianIsoCalcDelta.h"
#undef FIELDHERE
#undef NEXTFIELD
#undef HEREC
#undef HEREV
#undef MINUSJC

			/*Now that we have two deltas, make the normal*/
			++offset;

			v -> normal[0] = location * deltas[offset * 3];
			v -> normal[1] = location * deltas[offset * 3 + 1];
			v -> normal[2] = location * deltas[offset * 3 + 2];

			--offset;

			v -> normal[0] += location1 * deltas[offset * 3];
			v -> normal[1] += location1 * deltas[offset * 3 + 1];
			v -> normal[2] += location1 * deltas[offset * 3 + 2];

			r = sqrt(SQUARE(v -> normal[0]) +
				 SQUARE(v -> normal[1]) +
				 SQUARE(v -> normal[2]));
			if (r > 0.0)
			{
			    r = 1.0 / r;
			    v -> normal[0] *= r;
			    v -> normal[1] *= r;
			    v -> normal[2] *= r;

			    if (!encloseLow)
			    {
				v -> normal[0] = -(v -> normal[0]);
				v -> normal[1] = -(v -> normal[1]);
				v -> normal[2] = -(v -> normal[2]);
			    }
			}
			else
			{
			    /*Just give it plus z normal*/

			    v -> normal[0] = 0.0;
			    v -> normal[1] = 0.0;
			    v -> normal[2] = 1.0;
			}
		    }
		    else
		    {
			/*Just give it plus z normal*/

			v -> normal[0] = 0.0;
			v -> normal[1] = 0.0;
			v -> normal[2] = 1.0;
		    }

		    elements[offset * 3 + 1] = v;
		}
	    }
	    curVal = nextVal;
	    ++offset;
	}
    }

    /*Make K vertices*/
    index[0] = 0;
    index[1] = 0;
    index[2] = kVal;

    if (kVal < dims[2])
    for (index[0] = 0; index[0] < dims[0]; ++index[0])
    {
	offset = index[0] * dims[1];
	for (index[1] =0; index[1] < dims[1]; ++index[1])
	{
	    index[2] = kVal;
	    curVal = field[offset];

	    index[2] = kVal + 1;
	    nextVal = nextField[offset];

	    if ((curVal != missingData) &&
		(nextVal != missingData) &&
		BETWEENP(isoVal, curVal, nextVal))
		{
		    /*Yes!  Vertex here!*/
		    register VertexPtr v;
		    register real location, location1;
		    real curCoord[3], nextCoord[3];

		    v = NewVertex(pic, 0);
		    location = (isoVal - curVal) / (nextVal - curVal);
		    location1 = 1.0 - location;

		    /*Get coordinates*/
		    --index[2];
		    curCoord[0] = SelectFieldComponent(coord, 0, index);
		    curCoord[1] = SelectFieldComponent(coord, 1, index);
		    curCoord[2] = SelectFieldComponent(coord, 2, index);

		    ++index[2];
		    nextCoord[0] = SelectFieldComponent(coord, 0, index);
		    nextCoord[1] = SelectFieldComponent(coord, 1, index);
		    nextCoord[2] = SelectFieldComponent(coord, 2, index);

		    /*Set position*/
		    v -> position[0] = nextCoord[0] * location +
				       curCoord[0] * location1;
		    v -> position[1] = nextCoord[1] * location +
				       curCoord[1] * location1;
		    v -> position[2] = nextCoord[2] * location +
				       curCoord[2] * location1;

		    if (deltas)
		    {
			/*Calculate normal from delta*/
			--index[2];
#define FIELDHERE	field
#define NEXTFIELD	nextField
#define HEREC		curCoord
#define PLUSKC		nextCoord
#define HEREV		curVal
#include "ScianIsoCalcDelta.h"
#undef FIELDHERE
#undef NEXTFIELD
#undef HEREC
#undef PLUSKC
#undef HEREV
			v -> normal[0] = location1 * deltas[offset * 3];
			v -> normal[1] = location1 * deltas[offset * 3 + 1];
			v -> normal[2] = location1 * deltas[offset * 3 + 2];

			deltas[offset * 3] = missingData;
			deltas[offset * 3 + 1] = missingData;
			deltas[offset * 3 + 2] = missingData;

			++index[2];

#define FIELDHERE	nextField
#define PREVFIELD	field
#define HEREC		nextCoord
#define MINUSKC		curCoord
#define HEREV		nextVal
#include "ScianIsoCalcDelta.h"
#undef FIELDHERE
#undef PREVFIELD
#undef HEREC
#undef MINUSKC
#undef HEREV
			/*Now that we have two deltas, make the normal*/

			v -> normal[0] += location * deltas[offset * 3];
			v -> normal[1] += location * deltas[offset * 3 + 1];
			v -> normal[2] += location * deltas[offset * 3 + 2];

			r = sqrt(SQUARE(v -> normal[0]) +
				 SQUARE(v -> normal[1]) +
				 SQUARE(v -> normal[2]));
			if (r > 0.0)
			{
			    r = 1.0 / r;
			    v -> normal[0] *= r;
			    v -> normal[1] *= r;
			    v -> normal[2] *= r;

			    if (!encloseLow)
			    {
				v -> normal[0] = -(v -> normal[0]);
				v -> normal[1] = -(v -> normal[1]);
				v -> normal[2] = -(v -> normal[2]);
			    }
			}
			else
			{
			    /*Just give it plus z normal*/

			    v -> normal[0] = 0.0;
			    v -> normal[1] = 0.0;
			    v -> normal[2] = 1.0;
			}
		    }
		    else
		    {
			/*Just give it plus z normal*/

			v -> normal[0] = 0.0;
			v -> normal[1] = 0.0;
			v -> normal[2] = 1.0;
		    }

		    elements[offset * 3 + 2] = v;
		}
		else
		{
		    /*Haven't made deltas for next time, must kill them*/
		    if (deltas)
		    {
			    deltas[offset * 3] = missingData;
			    deltas[offset * 3 + 1] = missingData;
			    deltas[offset * 3 + 2] = missingData;
		    }
		}

	    curVal = nextVal;
	    ++offset;
	}
    }
}

static ObjPtr SetIsoMainDataset(visObj, dataSet)
ObjPtr visObj, dataSet;
/*Sets field to be the main field in visObj*/
{
   SetVar(visObj, MAINDATASET, 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*/
    ObjPtr checkBox;		/*Temporary check box*/
    ObjPtr titleBox;		/*Temporary title box*/
    ObjPtr radioGroup;		/*Group of radio buttons*/
    ObjPtr mainDataset;		/*The main dataset of the field*/
    ObjPtr scale;		/*Scale for slider*/

    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, MAINDATASET);
    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);
	SetVar(icon, REPOBJ, isoField);
    }
    else
    {
	icon = NewIcon(0, 0, ICONQUESTION, GetString(name));
    }
    SetVar(icon, ICONLOC, NULLOBJ);
    DropIconInCorral(corral, icon);

    ChooseGoodRange(&(db[0]), &(db[1]));

    /*Create the isovalue slider*/
    slider = TemplateSlider(IsosurfaceTemplate, "Isovalue", SCALE);
    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."));

    scale = TemplateScale(IsosurfaceTemplate, "Iso scale",
			SO_LEFT, false);
    PrefixList(panelContents, scale);
    SetVar(scale, PARENT, panelContents);
    LinkScale(scale, slider);
    
    MakeVar(object, ISOVAL);
    var = GetRealVar("AddIsoControls", object, ISOVAL);
    if (var)
    {
	initValue = GetReal(var);
    }
    else
    {
	initValue = (db[0] + db[1]) * 0.5;
	SetVar(object, ISOVAL, NewReal(initValue));
    }
    SetSliderRange(slider, db[1], db[0], 0.0);

    AssocDirectControlWithVar(slider, object, ISOVAL);
    SetTrackNot(slider, true);

    /*Create the value legend box*/
    textBox = TemplateTextBox(IsosurfaceTemplate, "Isovalue Text", ONE_LINE, "Isovalue:");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);

    top -= TEXTBOXHEIGHT + MINORBORDER;

    /*Create the value text box*/
    textBox = TemplateTextBox(IsosurfaceTemplate, "Isovalue Readout", EDITABLE + WITH_PIT + ONE_LINE, "Isovalue");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetTextAlign(textBox, RIGHTALIGN);
    SliderReadout(slider, textBox);

    /*Make radio buttons for enclosing*/

    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 = TemplateRadioButton(IsosurfaceTemplate, "High Values");
    SetVar(checkBox, HELPSTRING,
	NewString("This button specifies that the shapes of the isosurface  \
enclose values higher than the isosurface value."));
    AddRadioButton(radioGroup, checkBox);

    checkBox = TemplateRadioButton(IsosurfaceTemplate, "Low Values");
    SetVar(checkBox, HELPSTRING,
	NewString("This button specifies that the shapes of the isosurface \
enclose values lower than the isosurface value."));
    AddRadioButton(radioGroup, checkBox);

    /*Title box around it*/
    titleBox = TemplateTitleBox(IsosurfaceTemplate, "Enclose");
    PrefixList(panelContents, titleBox);
    SetVar(titleBox, PARENT, panelContents);

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

    /*Set its value*/
    if (!GetIntVar("AddIsoControls", object, ENCLOSELOW))
    {
	SetVar(object, ENCLOSELOW, NewInt(0));
    }
    AssocDirectControlWithVar(radioGroup, object, ENCLOSELOW);

    /*Get Normals*/
    titleBox = TemplateTitleBox(IsosurfaceTemplate, "Get Normals");
    PrefixList(panelContents, titleBox);
    SetVar(titleBox, PARENT, panelContents);

    radioGroup = NewRadioButtonGroup("Normal Radio");
    SetVar(radioGroup, HELPSTRING,
	NewString("These radio buttons allow you to specify whether the \
vertex normals of the resulting surface are taken from the geometry or \
from the data.  Taking the normals from the data is more accurate and \
produces a smoother surface.  \
Taking the normals from the geometry is faster.")); 

    checkBox = TemplateRadioButton(IsosurfaceTemplate, "From Data");
    SetVar(checkBox, HELPSTRING,
	NewString("This button specifies that the surface normals of the \
isosurface be taken from the gradient of the data, run through trilinear \
interpolation."));
    AddRadioButton(radioGroup, checkBox);

    checkBox = TemplateRadioButton(IsosurfaceTemplate, "From Geometry");
    SetVar(checkBox, HELPSTRING,
	NewString("This button specifies that the surface normals of the \
isosurface be calculated from the geometry using Phong lighting."));
    AddRadioButton(radioGroup, checkBox);
 
    /*Add the radio button group*/
    PrefixList(panelContents, radioGroup);
    SetVar(radioGroup, PARENT, panelContents);

    AssocDirectControlWithVar(radioGroup, object, NORMALSFROM);

    return ObjTrue;
}

#define NHIST	20
#define HISTFRACT 0.25
#define MAXDESIRABLE	2000

static ObjPtr MakeIsoVal(object)
ObjPtr object;
/*Makes the isoval of an object*/
{
    real db[2];
    real isoVal;
    ObjPtr repObj;
    ObjPtr minMax;
    long histogram[NHIST];
    int k;
    int nTraversalDims;
    long *traversalDims;
    long *traversalSteps;
    long *index;
    real d;
    long total;
    long runningTotal;
    int subDivision;
    int advantage;
    int whichDim;

    MakeVar(object, MAINDATASET);
    repObj = GetObjectVar("MakeIsoVal", object, MAINDATASET);
    if (!repObj)
    {
	return ObjFalse;
    }

    MakeVar(repObj, MINMAX);

    minMax = GetFixedArrayVar("MakeIsoVal", repObj, MINMAX, 1, 2L);

    if (!minMax)
    {
	return ObjFalse;
    }
    Array2CArray(db, minMax);
    ChooseGoodRange(&(db[0]), &(db[1]));

    /*Now we have the range, get the iso value*/
    SetCurField(FIELD1, repObj);

    /*Get the information on traversing the dataset*/
    nTraversalDims = CountTraversalDims(FIELD1);
    if (nTraversalDims)
    {
	traversalDims = (long *) Alloc(sizeof(long) * nTraversalDims);
	traversalSteps = (long *) Alloc(sizeof(long) * nTraversalDims);
	index = (long *) Alloc(sizeof(long) * nTraversalDims);
	GetTraversalDims(FIELD1, traversalDims);

	/*Figure out how coarsely to traverse dataset*/
	runningTotal = 0;
	advantage = 1;
	for (k = 0; k < nTraversalDims; ++k)
	{
	    runningTotal *= traversalDims[k];
	    advantage *= 2;
	}
	subDivision = 1;
	while (runningTotal > MAXDESIRABLE)
	{
	    ++subDivision;
	    runningTotal /= advantage;
	}

	/*Zero the histogram*/
	for (k = 0; k < NHIST; ++k)
	{
	    histogram[k] = 0;
	}

	/*Zero the index*/
	for (k = 0; k < nTraversalDims; ++k)
	{
	    index[k] = 0;
	}

	/*Fill up the histogram*/
	do
	{
	    d = SelectFieldScalar(FIELD1, index);
	    if (d != missingData)
	    {
		k = ((real) NHIST) * (d - db[0]) / (db[1] - db[0]);
		if (k < 0) k = 0;
		if (k >= NHIST) k = NHIST - 1;
		++histogram[k];
	    }

	    /*Advance to next sample*/
	    for (whichDim = 0; whichDim < nTraversalDims; ++whichDim)
	    {
		if (traversalDims[whichDim] > 0)
		{
		    index[whichDim] += subDivision;
		    if (index[whichDim] >= traversalDims[whichDim])
		    {
			index[whichDim] = 0;
		    }
		    else
		    {
			break;
		    }
		}
	    }
	} while (whichDim < nTraversalDims); /*Break is based on advance*/

	/*Now choose from histogram*/
	total = 0;
	for (k = 0; k < NHIST; ++k)
	{
	    total += histogram[k];
	}
	runningTotal = 0;
	for (k = NHIST - 1; k > 1; --k)
	{
	    runningTotal += histogram[k];
	    if (runningTotal > HISTFRACT * total)
	    {
		break;
	    }
	}

	isoVal = k * (db[1] - db[0]) / NHIST + db[0];

	SAFEFREE(index);
	SAFEFREE(traversalDims);
	SAFEFREE(traversalSteps);
    }
    else
    {
	isoVal = (db[0] + db[1]) * 0.5;
    }

    SetVar(object, ISOVAL, NewReal(isoVal));
    return ObjTrue;
}

Bool Escape()
{
    int x, y;
    if (runningScript)
    {
	return false;
    }
    else
    {
	return Mouse(&x, &y);
    }
}

ObjPtr NewMakeIsoSurface(object)
ObjPtr object;
/*Makes a SURFACE in isosurface vis object*/
{
    ObjPtr repObj;
    long dims[3];
    long index[3];
    double time;
    ObjPtr var;
    real isoVal;
    Bool encloseLow;
    ObjPtr surface;
    int isoLevel;
    ObjPtr lastIJK, nextIJK;
    ObjPtr lastData, nextData, nextPlusData;
    Bool useDeltas;
    ObjPtr deltas;

    time = Clock();

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

    /*Get the iso value*/
    MakeVar(object, ISOVAL);
    var = GetRealVar("MakeIsoSurface", object, ISOVAL);
    if (!var)
    {
	return ObjFalse;
    }
    isoVal = GetReal(var);

    encloseLow = GetPredicate(object, ENCLOSELOW);

    useDeltas = GetPredicate(object, NORMALSFROM) ? false : true;

    LongOperation();

    /*Get the isolevel to start at*/
    var = GetVar(object, ISOLEVEL);
    if (var)
    {
	isoLevel = GetInt(var);
    }
    else
    {
	isoLevel = 0;
    }

    SetCurField(FIELD1, repObj);
    SetCurForm(FORMFIELD, repObj);
    if (CountTraversalDims(FIELD1) != 3)
    {
	ReportError("MakeIsoSurface", "Wrong number of traversal dimensions");
	return ObjFalse;
    }
    GetTraversalDims(FIELD1, dims);

    if (isoLevel)
    {
	/*This is just a continuation of a previous incarnation*/
	if (isoLevel >= dims[2])
	{
	    /*Escape clause; just return*/
	    return ObjTrue;
	}
	surface = GetVar(object, SURFACE);
	if (!surface)
	{
	    surface = NewPicture();
	    SetVar(object, SURFACE, surface);
	}

	lastIJK = GetVar(object, LASTIJKVERTICES);
	if (!lastIJK)
	{
	    ReportError("MakeIsoSurface", "No LASTIJKVERTICES");
	    return ObjFalse;
	}

	nextIJK = GetVar(object, NEXTIJKVERTICES);
	if (!nextIJK)
	{
	    ReportError("MakeIsoSurface", "No NEXTIJKVERTICES");
	    return ObjFalse;
	}

	lastData = GetVar(object, LASTDATA);
	if (!lastData)
	{
	    ReportError("MakeIsoSurface", "No LASTDATA");
	    return ObjFalse;
	}

	nextData = GetVar(object, NEXTDATA);
	if (!nextData)
	{
	    ReportError("MakeIsoSurface", "No NEXTDATA");
	    return ObjFalse;
	}

	deltas = GetVar(object, DELTAS);
    }
    else
    {
	long arrayDims[3];
	real *elements;
	long nElements;

	surface = NewPicture();
	SetVar(surface, REPOBJ, object);
	SetVar(object, SURFACE, surface);

	arrayDims[0] = dims[0];
	arrayDims[1] = dims[1];
	arrayDims[2] = 3;

	/*Make data arrays*/
	lastData = NewArray(AT_REAL, 2, arrayDims);
	SetVar(object, LASTDATA, lastData);

	nextData = NewArray(AT_REAL, 2, arrayDims);
	SetVar(object, NEXTDATA, nextData);

	if (useDeltas)
	{
	    deltas = NewArray(AT_REAL, 3, arrayDims);

	    /*Make them all missing*/
	    nElements = arrayDims[0] * arrayDims[1] * arrayDims[2];
	    elements = ELEMENTS(deltas);
	    do
	    {
		--nElements;
		elements[nElements] = missingData;
	    } while (nElements);
	}
	else
	{
	    deltas = NULLOBJ;
	}

	/*Fill in the data*/
	index[0] = 0;
	index[1] = 0;
	index[2] = 0;
	elements = ELEMENTS(lastData);
	StuffIJPlane(elements, FIELD1, 0, index);

	/*Get last data*/
	index[0] = 0;
	index[1] = 0;
	index[2] = 0;
	StuffIJPlane((real *) ELEMENTS(lastData), FIELD1, 0, index);

	/*Get next data*/
	index[0] = 0;
	index[1] = 0;
	index[2] = 1;
	StuffIJPlane((real *) ELEMENTS(nextData), FIELD1, 0, index);

	/*Make IJK arrays*/
	lastIJK = NewArray(AT_POINTER, 3, arrayDims);
	SetVar(object, LASTIJKVERTICES, lastIJK);

	nextIJK = NewArray(AT_POINTER, 3, arrayDims);
	SetVar(object, NEXTIJKVERTICES, nextIJK);

	/*Fill in just the lastIJK*/
	StuffIJKVertices(ELEMENTS(lastIJK), ELEMENTS(lastData), ELEMENTS(nextData), deltas ? ELEMENTS(deltas) : 0, FIELD1, FORMFIELD, dims, 0, surface, isoVal, encloseLow);
    }

    nextPlusData = NewArray(AT_REAL, 2, dims);

    /*Go through and do the isosurface*/
    while (isoLevel < dims[2] - 1)
    {
	index[0] = 0;
	index[1] = 0;
	index[2] = isoLevel + 2;

	/*Get next plus data*/
	if (isoLevel < dims[2] - 2)
	{
	    StuffIJPlane((real *) ELEMENTS(nextPlusData), FIELD1, 0, index);
	}

	/*Get next IJK's*/
	StuffIJKVertices(ELEMENTS(nextIJK), ELEMENTS(nextData), ELEMENTS(nextPlusData), deltas ? ELEMENTS(deltas) : 0, FIELD1, FORMFIELD, dims, isoLevel + 1, surface, isoVal, encloseLow);

	/*Now go through and make the isosurface*/
	for (index[0] = 0; index[0] < dims[0] - 1; ++index[0])
	{
	    for (index[1] = 0; index[1] < dims[1] - 1; ++index[1])
	    {
		NewIsoHex(ELEMENTS(lastData), ELEMENTS(nextData),
			ELEMENTS(lastIJK), ELEMENTS(nextIJK),
			index, dims,
			isoVal, surface, encloseLow);
	    }
	}

	/*Shift down ijk vertices*/
	var = lastIJK;
	lastIJK = nextIJK;
	nextIJK = var;

	/*Shift down data*/
	var = lastData;
	lastData = nextData;
	nextData = nextPlusData;
	nextPlusData = var;

	/*Go to next isoLevel*/
	++isoLevel;

#if 0
	/*Insert escape code here*/
	if (Escape())
	{
	    SetVar(object, LASTIJKVERTICES, lastIJK);
	    SetVar(object, NEXTIJKVERTICES, nextIJK);
	    SetVar(object, LASTDATA, lastData);
	    SetVar(object, NEXTDATA, nextData);
	    SetVar(object, ISOLEVEL, NewInt(isoLevel));
	    SetVar(object, DELTAS, deltas);
	    DeferMessage(object, MAKEISOSURFACE);
	    return ObjFalse;
	}
#endif
    }

    if (!deltas)
    {
	CalcPictureNormals(surface);
    }

    SetVar(object, SURFACE, surface);
    MakeVar(object, CPALETTE);
    SetVar(surface, CPALETTE, GetVar(object, CPALETTE));

    return ObjTrue;
}

static ObjPtr MakeIsoSurfaceLater(object)
ObjPtr object;
/*Sets up to make an isosurface later*/
{
    FuncTyp method;

    SetVar(object, ISOLEVEL, NewInt(0));
    method = GetMethod(object, MAKEISOSURFACE);

    if (method)
    {
	(*method)(object);
    }
    return ObjTrue;
}

static ObjPtr IsoInit(iso)
ObjPtr iso;
/*Initializes an isosurface*/
{
    ObjPtr mainDataset;
    long nDims;
    long *dims;
    ObjPtr retVal;

    MakeVar(iso, MAINDATASET);
    mainDataset = GetVar(iso, MAINDATASET);
    if (!mainDataset) return NULLOBJ;

    SetCurForm(FORMFIELD, mainDataset);
    nDims = CountTraversalDims(FORMFIELD);
    if (nDims != 3) return NULLOBJ;

    dims = Alloc(3 * sizeof(long));
    GetTraversalDims(FORMFIELD, dims);

    retVal = ObjTrue;

    if (dims[0] <= 1) retVal = ObjFalse;
    if (dims[1] <= 1) retVal = ObjFalse;
    if (dims[2] <= 1) retVal = ObjFalse;

    return retVal;
}

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(isoClass, NORMALSFROM, NewInt(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);
    SetMethod(isoClass, INITIALIZE, IsoInit);
    SetVar(isoClass, SAVEEXTENSION, NewString("isoSurface"));
    SetVar(isoClass, HANDLEMISSING, NewInt(0));
    SetMethod(isoClass, ISOVAL, MakeIsoVal);
    DeclareDependency(isoClass, SURFACE, ISOVAL);
    DeclareDependency(isoClass, SURFACE, ENCLOSELOW);
    SetVar(isoClass, ENCLOSELOW, NewInt(0));
    DeclareIndirectDependency(isoClass, SURFACE, MAINDATASET, CHANGED);
    DeclareDependency(isoClass, SURFACE, MAINDATASET);
    DeclareDependency(isoClass, SURFACE, NORMALSFROM);
    SetMethod(isoClass, MAKEISOSURFACE, NewMakeIsoSurface);
    SetMethod(isoClass, SURFACE, MakeIsoSurfaceLater);

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

void KillIsosurfaces()
{
    DeleteThing(isoClass);
}
