/*ScianVisArrows.c
  June 14, 1991
  Eric Pepke
  Routines for arrows visualization object

  The arrow object takes a vector field defined over a data form with
  any number of topological dimensions, 2 or 3 spatial dimensions, and 2 
  or 3 components and draws arrows at each point.  The arrow visualization
  object works for regular data forms, curvilinear data forms, and
  nonstructured data forms.

*/

#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 "ScianLists.h"
#include "ScianSpaces.h"
#include "ScianSliders.h"
#include "ScianIDs.h"
#include "ScianDatasets.h"
#include "ScianErrors.h"
#include "ScianVisObjects.h"
#include "ScianVisWindows.h"
#include "ScianVisArrows.h"
#include "ScianStyle.h"
#include "ScianPictures.h"
#include "ScianTitleBoxes.h"
#include "ScianButtons.h"
#include "ScianMethods.h"
#include "ScianTemplates.h"
#include "ScianTemplateHelper.h"
#include "ScianSymbols.h"

ObjPtr arrowsClass;

static ObjPtr ArrowsInit(object)
ObjPtr object;
/*Initializes an arrows object*/
{
    real bounds[6];

    if (GetBounds(object, bounds))
    {
	real size;
	size = bounds[1] - bounds[0];
	size = MAX(size, bounds[3] - bounds[2]);
	size = MAX(size, bounds[5] - bounds[4]);

	SetVar(object, HEADWIDTH, NewReal(size * 0.01));
	SetVar(object, SHAFTWIDTH, NewReal(size * 0.005));
	SetVar(object, HEADLENGTH, NewReal(size * 0.03));

	/****UPDATE*** determine heuristically from dataset*/
	SetVar(object, VECTORFACTOR, NewReal(1.0));
    }
    else
    {
	SetVar(object, HEADWIDTH, NewReal(0.01));
	SetVar(object, SHAFTWIDTH, NewReal(0.005));
	SetVar(object, HEADLENGTH, NewReal(0.03));

	SetVar(object, VECTORFACTOR, NewReal(0.5));
    }
}

static ObjPtr SetArrowsMainDataset(visObj, dataSet)
ObjPtr visObj, dataSet;
/*Sets the main data set of visObj to dataSet*/
{
    SetVar(visObj, MAINDATASET, dataSet);
    return ObjTrue;
}

static ObjPtr MakeArrowsColored(visObject)
ObjPtr visObject;
/*Makes the arrows colored*/
{
    SetVar(visObject, PICCOLORED, ObjTrue);
    if (GetPredicate(visObject, COLORS))
    {
	ObjPtr colorField;
	real brightness;
	ObjPtr var;
	ObjPtr surface;
	ObjPtr palette;

	/*Get the color field and its form*/
	colorField = GetVar(visObject, COLOROBJ);
	if (!colorField) return ObjFalse;

	/*Get the color palette*/
	MakeVar(colorField, CPALETTE);
	palette = GetPaletteVar("MakeArrowsColored", colorField, CPALETTE);
	if (!palette)
	{
	    return ObjFalse;
	}
	SetPalette(palette);

	/*Get the surface to color*/
	surface = GetPictureVar("MakeArrowsColored", visObject, SURFACE);
	if (!surface) return ObjFalse;

	/*Have to make it colored by the object*/
	ColorPictureByObject(surface, colorField, GetPredicate(visObject, INTERPCOLORS));
    }
    return ObjTrue;
}

static int curNSubdivisions = 3;
static Bool curCapEnds = true;
static int arrowStyle;

void EmitArrows(ObjPtr, int, long dims[], long indices[], real, real, real, real, int);

#ifdef PROTO
void EmitArrows(ObjPtr picture, int nDims, long dims[], long indices[],
		real vecFactor, real shaftWidth, real headWidth, real headLength, int whichIndex)
#else
void EmitArrows(picture, nDims, dims, indices, vecFactor, shaftWidth, headWidth, headLength, whichIndex)
ObjPtr picture;
int nDims;
long dims[], indices[];
real vecFactor;
real shaftWidth, headWidth, headLength;
int whichIndex;
#endif
/*Emits a bunch of arrows into picture, using recursive descent to get all
  combinations of indices within dims.  Gets its start from FORMFIELD and it's
  finish from FIELD1.  Depth is the current index */
{
    if (whichIndex >= nDims)
    {
	real x1, y1, z1, vx, vy, vz, len, nx, ny, nz;
	int nComponents;

	/*Down to the end.  Make an arrow*/

	/*Get the beginning*/
	nComponents = GetNComponents(FORMFIELD);
	x1 = SelectFieldComponent(FORMFIELD, 0, indices);
	y1 = SelectFieldComponent(FORMFIELD, 1, indices);
	if (nComponents < 3)
	{
	    z1 = 0.0;
	}
	else
	{
	    z1 = SelectFieldComponent(FORMFIELD, 2, indices);
	}

	/*Get the vector*/
	nComponents = GetNComponents(FIELD1);
	vx = SelectFieldComponent(FIELD1, 0, indices);
	vy = SelectFieldComponent(FIELD1, 1, indices);
	if (nComponents < 3)
	{
	    vz = 0.0;
	}
	else
	{
	    vz = SelectFieldComponent(FIELD1, 2, indices);
	}

	/*Do nothing for missing data*/
	if (vx == missingData ||
	    vy == missingData ||
	    vz == missingData)
	{
	    return;
	}

	/*Multiply by vector factor*/
	vx *= vecFactor;
	vy *= vecFactor;
	vz *= vecFactor;

	/*Get the length*/
	len = sqrt(vx * vx + vy * vy + vz * vz);

	/*And the normalized vector*/
	nx = vx / len;
	ny = vy / len;
	nz = vz / len;

	switch(arrowStyle)
	{
	    case AS_LINE:
		{
		    Vertex vertices[2];
		    vertices[0] . normal[0] = 0.0;
		    vertices[0] . normal[1] = 0.0;
		    vertices[0] . normal[2] = 1.0;
		    vertices[0] . position[0] = x1;
		    vertices[0] . position[1] = y1;
		    vertices[0] . position[2] = z1;
		    vertices[0] . colorIndex = 0;

		    vertices[1] . normal[0] = 0.0;
		    vertices[1] . normal[1] = 0.0;
		    vertices[1] . normal[2] = 1.0;
		    vertices[1] . position[0] = x1 + vx;
		    vertices[1] . position[1] = y1 + vy;
		    vertices[1] . position[2] = z1 + vz;
		    vertices[1] . colorIndex = 0;

		    AppendPolylineToPicture(picture, 1, 0, 2, vertices);
		}
		break;
	    case AS_FLAT:
		{
		    int k;
		    float r[3];		/*Radial unit vector*/
		    float n[3];		/*Normal unit vector*/
		    float a[3];		/*Axial unit vector*/
		    double length, nf;

		    a[0] = vx;
		    a[1] = vy;
		    a[2] = vz;

		    length = sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2]);
		    if (length <= 0.0)
		    {
			/*Zero length vector, do nothing*/
			break;
		    }

		    nf = 1.0 / length;
		    a[0] *= nf; a[1] *= nf; a[2] *= nf;

		    /*See if it's nearly colinear with k*/
		    if (ABS(a[2]) > 0.8)
		    {
			/*It is, so cross by j to get first r*/
			r[0] = -a[2];
			r[1] = 0.0;
			r[2] = a[0];
		    }
		    else
		    {
			/*It isn't, so cross by k to get first r*/
			r[0] = -a[1];
			r[1] = a[0];
			r[2] = 0;
		    }

		    NORMALIZE(r);

		    /*Cross a with first radial unit to get orthogonal unit*/
		    CROSS(a, r, n);

		    /*Fill in the vertices and draw*/

		    if (headLength >= len)
		    {
			/*There's no shaft*/
			Vertex vertices[3];

			/*Set up vertices*/
			for (k = 0; k < 3; ++k)
			{
			    vertices[k] . normal[0] = n[0]; 
			    vertices[k] . normal[1] = n[1]; 
			    vertices[k] . normal[2] = n[2]; 
			    vertices[k] . colorIndex = 0;
			}

			/*Arrow edge*/
			vertices[0] . position[0] = x1 - r[0] * headWidth;
			vertices[0] . position[1] = y1 - r[1] * headWidth;
			vertices[0] . position[2] = z1 - r[2] * headWidth;

			/*Arrow tip*/
			vertices[1] . position[0] = x1 + vx;
			vertices[1] . position[1] = y1 + vy;
			vertices[1] . position[2] = z1 + vz;

			/*Arrow edge*/
			vertices[2] . position[0] = x1 + r[0] * headWidth;
			vertices[2] . position[1] = y1 + r[1] * headWidth;
			vertices[2] . position[2] = z1 + r[2] * headWidth;

			AppendPolyToPicture(picture, 3, vertices);
		    }
		    else
		    {
			Vertex vertices[7];

			/*Set up vertices*/
			for (k = 0; k < 7; ++k)
			{
			    vertices[k] . normal[0] = n[0]; 
			    vertices[k] . normal[1] = n[1]; 
			    vertices[k] . normal[2] = n[2]; 
			    vertices[k] . colorIndex = 0;
			}

			/*Base corner*/
			vertices[0] . position[0] = x1 - r[0] * shaftWidth;
			vertices[0] . position[1] = y1 - r[1] * shaftWidth;
			vertices[0] . position[2] = z1 - r[2] * shaftWidth;

			/*Arrow armpit*/
			vertices[1] . position[0] = x1 + vx - headLength * a[0] - r[0] * shaftWidth;
			vertices[1] . position[1] = y1 + vy - headLength * a[1] - r[1] * shaftWidth;
			vertices[1] . position[2] = z1 + vz - headLength * a[2] - r[2] * shaftWidth;

			/*Arrow edge*/
			vertices[2] . position[0] = x1 + vx - headLength * a[0] - r[0] * headWidth;
			vertices[2] . position[1] = y1 + vy - headLength * a[1] - r[1] * headWidth;
			vertices[2] . position[2] = z1 + vz - headLength * a[2] - r[2] * headWidth;

			/*Arrow tip*/
			vertices[3] . position[0] = x1 + vx;
			vertices[3] . position[1] = y1 + vy;
			vertices[3] . position[2] = z1 + vz;

			/*Arrow edge*/
			vertices[4] . position[0] = x1 + vx - headLength * a[0] + r[0] * headWidth;
			vertices[4] . position[1] = y1 + vy - headLength * a[1] + r[1] * headWidth;
			vertices[4] . position[2] = z1 + vz - headLength * a[2] + r[2] * headWidth;

			/*Arrow armpit*/
			vertices[5] . position[0] = x1 + vx - headLength * a[0] + r[0] * shaftWidth;
			vertices[5] . position[1] = y1 + vy - headLength * a[1] + r[1] * shaftWidth;
			vertices[5] . position[2] = z1 + vz - headLength * a[2] + r[2] * shaftWidth;

			/*Base corner*/
			vertices[6] . position[0] = x1 + r[0] * shaftWidth;
			vertices[6] . position[1] = y1 + r[1] * shaftWidth;
			vertices[6] . position[2] = z1 + r[2] * shaftWidth;

			AppendPolyToPicture(picture, 7, vertices);
		    }
		}
		break;
	    case AS_WIRE:
		{
		    int k;
		    float r[3];		/*Radial unit vector*/
		    float n[3];		/*Normal unit vector*/
		    float a[3];		/*Axial unit vector*/
		    double length, nf;

		    a[0] = vx;
		    a[1] = vy;
		    a[2] = vz;

		    length = sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2]);
		    if (length <= 0.0)
		    {
			/*Zero length vector, do nothing*/
			break;
		    }

		    nf = 1.0 / length;
		    a[0] *= nf; a[1] *= nf; a[2] *= nf;

		    /*See if it's nearly colinear with k*/
		    if (ABS(a[2]) > 0.8)
		    {
			/*It is, so cross by j to get first r*/
			r[0] = -a[2];
			r[1] = 0.0;
			r[2] = a[0];
		    }
		    else
		    {
			/*It isn't, so cross by k to get first r*/
			r[0] = -a[1];
			r[1] = a[0];
			r[2] = 0;
		    }

		    NORMALIZE(r);

		    /*Cross a with first radial unit to get orthogonal unit*/
		    CROSS(a, r, n);

		    /*Fill in the vertices and draw*/

		    if (headLength >= len)
		    {
			/*There's no shaft*/
			Vertex vertices[5];

			/*Set up vertices*/
			for (k = 0; k < 5; ++k)
			{
			    vertices[k] . normal[0] = n[0]; 
			    vertices[k] . normal[1] = n[1]; 
			    vertices[k] . normal[2] = n[2]; 
			    vertices[k] . colorIndex = 0;
			}

			/*Base*/
			vertices[0] . position[0] = x1;
			vertices[0] . position[1] = y1;
			vertices[0] . position[2] = z1;

			/*Arrow tip*/
			vertices[1] . position[0] = x1 + vx;
			vertices[1] . position[1] = y1 + vy;
			vertices[1] . position[2] = z1 + vz;

			/*Arrow edge*/
			vertices[2] . position[0] = x1 - r[0] * headWidth;
			vertices[2] . position[1] = y1 - r[1] * headWidth;
			vertices[2] . position[2] = z1 - r[2] * headWidth;

			/*Arrow edge*/
			vertices[3] . position[0] = x1 + r[0] * headWidth;
			vertices[3] . position[1] = y1 + r[1] * headWidth;
			vertices[3] . position[2] = z1 + r[2] * headWidth;

			/*Arrow tip*/
			vertices[4] . position[0] = x1 + vx;
			vertices[4] . position[1] = y1 + vy;
			vertices[4] . position[2] = z1 + vz;

			AppendPolylineToPicture(picture, 1, 0, 3, vertices);
			AppendPolylineToPicture(picture, 1, 0, 2, &(vertices[3]));
		    }
		    else
		    {
			Vertex vertices[5];

			/*Set up vertices*/
			for (k = 0; k < 5; ++k)
			{
			    vertices[k] . normal[0] = n[0]; 
			    vertices[k] . normal[1] = n[1]; 
			    vertices[k] . normal[2] = n[2]; 
			    vertices[k] . colorIndex = 0;
			}

			/*Base*/
			vertices[0] . position[0] = x1;
			vertices[0] . position[1] = y1;
			vertices[0] . position[2] = z1;

			/*Arrow tip*/
			vertices[1] . position[0] = x1 + vx;
			vertices[1] . position[1] = y1 + vy;
			vertices[1] . position[2] = z1 + vz;

			/*Arrow edge*/
			vertices[2] . position[0] = x1 + vx - headLength * a[0] - r[0] * headWidth;
			vertices[2] . position[1] = y1 + vy - headLength * a[1] - r[1] * headWidth;
			vertices[2] . position[2] = z1 + vz - headLength * a[2] - r[2] * headWidth;

			/*Arrow edge*/
			vertices[3] . position[0] = x1 + vx - headLength * a[0] + r[0] * headWidth;
			vertices[3] . position[1] = y1 + vy - headLength * a[1] + r[1] * headWidth;
			vertices[3] . position[2] = z1 + vz - headLength * a[2] + r[2] * headWidth;

			/*Arrow tip*/
			vertices[4] . position[0] = x1 + vx;
			vertices[4] . position[1] = y1 + vy;
			vertices[4] . position[2] = z1 + vz;

			AppendPolylineToPicture(picture, 1, 0, 3, vertices);
			AppendPolylineToPicture(picture, 1, 0, 2, &(vertices[3]));
		    }
		}
		break;
	    case AS_SOLID:
		if (headLength >= len)
		{
		    /*There's no shaft*/
		    float end1[3], rad1, end2[3], rad2;

		    end1[0] = x1;
		    end1[1] = y1;
		    end1[2] = z1;
		    rad1 = headWidth;
		    end2[0] = x1 + vx;
		    end2[1] = y1 + vy;
		    end2[2] = z1 + vz;
		    rad2 = 0.0;
		    ConvertFrustumOntoPicture(picture, end1, rad1, end2, rad2, curNSubdivisions, curCapEnds);
		}
		else
		{
		    /*There is a shaft*/
		    float end1[3], rad1, end2[3], rad2;

		    end1[0] = x1;
		    end1[1] = y1;
		    end1[2] = z1;
		    rad1 = shaftWidth;
		    end2[0] = x1 + nx * (len - headLength);
		    end2[1] = y1 + ny * (len - headLength);
		    end2[2] = z1 + nz * (len - headLength);
		    rad2 = shaftWidth;
		    ConvertFrustumOntoPicture(picture, end1, rad1, end2, rad2, curNSubdivisions, curCapEnds);
		    rad2 = headWidth;
		    end1[0] = x1 + vx;
		    end1[1] = y1 + vy;
		    end1[2] = z1 + vz;
		    rad1 = 0.0;
		    ConvertFrustumOntoPicture(picture, end1, rad1, end2, rad2, curNSubdivisions, curCapEnds);
		}
		break;
	}
    }
    else
    {
	for (indices[whichIndex] = 0;
	     indices[whichIndex] < dims[whichIndex];
	     ++indices[whichIndex])
	{
	    EmitArrows(picture, nDims, dims, indices, vecFactor, shaftWidth, headWidth, headLength, whichIndex + 1);
	} 
    }
}

static ObjPtr MakeArrowsSurface(visObject)
ObjPtr visObject;
/*Makes the surface in a arrows object.  Also colors it.*/
{
    ObjPtr dataset;		/*The dataset the vis object represents*/
    long datasetFlags;		/*Flags of the dataset*/
    ObjPtr var;			/*Random variable*/
    ObjPtr picture;		/*The picture to be made*/
    real shaftWidth, headWidth, headLength, vecFactor;

    dataset = GetObjectVar("MakeArrowsSurface", visObject, MAINDATASET);
    if (!dataset)
    {
	return ObjFalse;
    }

    datasetFlags = GetDatasetInfo(dataset);

    if (0 == datasetFlags & DS_HASFORM)
    {
	ReportError("MakeArrowsSurface", "No data form");
	return ObjFalse;
    }

    /*Make the new picture*/
    picture = NewPicture();
    if (!picture) return ObjFalse;

    var = GetRealVar("MakeArrowsSurface", visObject, SHAFTWIDTH);
    if (!var)
    {
	return ObjFalse;
    }
    shaftWidth = GetReal(var);

    var = GetRealVar("MakeArrowsSurface", visObject, HEADWIDTH);
    if (!var)
    {
	return ObjFalse;
    }
    headWidth = GetReal(var);

    var = GetRealVar("MakeArrowsSurface", visObject, HEADLENGTH);
    if (!var)
    {
	return ObjFalse;
    }
    headLength = GetReal(var);

    var = GetRealVar("MakeArrowsSurface", visObject, VECTORFACTOR);
    if (!var)
    {
	return ObjFalse;
    }
    vecFactor = GetReal(var);

    if (datasetFlags & DS_UNSTRUCTURED)
    {
	/*It's an unstructured dataset. Do nothing now.*/
#if 0
	ObjPtr nodes;
	long nNodes;
	threeArray *nodesArray;		/*Array of nodes*/
	ThingListPtr runner;		/*The runner through the cells*/

	nodes = GetDatasetNodes(dataset);
	if (!nodes)
	{
	    return ObjFalse;
	}
	nodesArray = (threeArray *) ArrayMeat(nodes);
	nNodes = DIMS(nodes
	    runner = LISTOF(faces);    
	    while (runner)
	    {
		ObjPtr array;
		int k, d, i;
		real *a;

		array = runner -> thing;
		d = DIMS(array)[0];
		a = ArrayMeat(array);


		for (k = 0; k < d; ++k)
		{
		    i = *a;
		    
		    if (k >= allocVertices)
		    {
			allocVertices += 20;
			vertices = (VertexPtr) realloc(vertices, allocVertices * sizeof(Vertex));
			if (!vertices)
			{
			    return ObjFalse;
			}
		    }
		    vertices[k] . position[0] = nodesArray[i][0];
		    vertices[k] . position[1] = nodesArray[i][1];
		    vertices[k] . position[2] = nodesArray[i][2];

		    /****UPDATE*** assume normal in z direction only*/
		    vertices[k] . normal[0] = 0.0;
		    vertices[k] . normal[1] = 0.0;
		    vertices[k] . normal[2] = 1.0;

		    ++a;
		}
		AppendPolyToPolys(polys, d, vertices);

		runner = runner -> next;
	    }
	    free(vertices);
	}
#endif
    }
    else
    {
	/*It's a structured dataset.*/
	int topDim, nComponents;
	int k;
	ObjPtr dims;
	RectMeshPtr rectMesh;
	real temp[3];
	Bool zDisplaced;
	real *elements;
	long *iDims, *iIndices;
	
	/*Any topological dimension*/
	topDim = GetTopDim(dataset);

	/*Get the actual topological dimensions*/
	dims = GetDatasetFormDims(dataset);

	/*Register the dataset and its dataform*/
	if (!SetCurField(FIELD1, dataset))
	{
	    return ObjFalse;
	}
	if (!SetCurForm(FORMFIELD, dataset))
	{
	    return ObjFalse;
	}

	/*Set up the initial array of dimensions*/
	if (topDim)
	{
	    iDims = (long *) malloc(topDim * sizeof(long));
	    iIndices = (long *) malloc(topDim * sizeof(long));
	    elements = ELEMENTS(dims);
	    for (k = 0; k < topDim; ++k)
	    {
		iDims[k] = (long) (elements[k] + 0.5);
		iIndices[k] = 0;
	    }
	}
	else
	{
	    iDims = 0;
	    iIndices = 0;
	    elements = 0;
	}
	var = GetIntVar("MakeArrowsSurface", visObject, FRUSTUMSUBDIV);
	if (var)
	{
	    curNSubdivisions = GetInt(var);
	}
	else
	{
	    curNSubdivisions = 3;
	}
	curCapEnds = GetPredicate(visObject, CAPENDSP);
	var = GetVar(visObject, STYLE);
	if (var)
	{
	    arrowStyle = GetInt(var);
	}
	else
	{
	    arrowStyle = 0;
	}

	EmitArrows(picture, topDim, iDims, iIndices, vecFactor, shaftWidth, headWidth, headLength, 0);
    }

    SetVar(visObject, SURFACE, picture);
    SetVar(picture, REPOBJ, visObject);
    return ObjTrue;
}

static ObjPtr ChangeArrowStyle(radio)
ObjPtr radio;
/*Changes the style of a bunch of arrows according to radio*/
{
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeArrowStyle", radio, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    SetVar(repObj, STYLE, GetValue(radio));
    ImInvalid(repObj);
    return ObjTrue;
}

static ObjPtr AddArrowsControls(arrows, panelContents)
ObjPtr arrows, panelContents;
/*Adds controls appropriate to an arrows object to panelContents*/
{
    ObjPtr titleBox, button, radio, var, corral, icon, name, arrowsField, mainDataset;
    ObjPtr textBox, defaultIcon;
    int width, left, top, bottom, right, mid;
    int style;

    /*Put in the arrow corral at the top left*/
    left = MAJORBORDER;
    top = MAJORBORDER;
    corral = NewIconCorral(NULLOBJ,
			   left, left + ONECORRALWIDTH,
			   CWINHEIGHT - MAJORBORDER - ONECORRALHEIGHT,
			   CWINHEIGHT - MAJORBORDER, 0);
    SetVar(corral, SINGLECORRAL, ObjTrue);
    SetVar(corral, TOPDOWN, ObjTrue);
    SetVar(corral, NAME, NewString("Arrows Field"));
    SetVar(corral, HELPSTRING,
	NewString("This corral shows the dataset that is being used to make \
the arrows display.  The locations, directions, and lengths of the arrows \
are calculated using this field."));
    PrefixList(panelContents, corral);
    SetVar(corral, PARENT, panelContents);
    SetVar(corral, REPOBJ, arrows);
    SetMethod(corral, DROPINCONTENTS, DropInMainDatasetCorral);

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

    /*Put in an icon that represents the field*/
    arrowsField = GetObjectVar("AddArrowsControls", arrows, MAINDATASET);
    if (!arrowsField) return ObjFalse;
    while (mainDataset = GetVar(arrowsField, MAINDATASET))
    {
	arrowsField = mainDataset;
    }

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

    top = CWINHEIGHT - MAJORBORDER - ONECORRALHEIGHT - TEXTBOXSEP - TEXTBOXHEIGHT - MINORBORDER;
    left = MAJORBORDER;

    /*Create the arrow length factor text box*/
    mid = top - EDITBOXHEIGHT / 2;
    textBox = NewTextBox(left, left + ACNAMEWIDTH,
			 mid - TEXTBOXHEIGHT / 2 + EDITBOXDOWN,
			 mid + TEXTBOXHEIGHT / 2 + EDITBOXDOWN,
			 0, "Arrow Length Text", "Arrow Length:");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);

    var = GetRealVar("AddArrowControls", arrows, VECTORFACTOR);
    if (!var)
    {
	return ObjFalse;
    }
    sprintf(tempStr, "%g", GetReal(var));

    textBox = NewTextBox(left + ACNAMEWIDTH + MINORBORDER,
			 left + ACNAMEWIDTH + MINORBORDER + ACTEXTWIDTH,
			 mid - EDITBOXHEIGHT / 2, mid + EDITBOXHEIGHT / 2,
			 EDITABLE + WITH_PIT + ONE_LINE, "Arrow Length Number", tempStr);
    SetVar(textBox, PARENT, panelContents);
    PrefixList(panelContents, textBox);
    SetTextAlign(textBox, RIGHTALIGN);
    SetVar(textBox, REPOBJ, arrows);
    SetVar(textBox, HELPSTRING, NewString("This text box alters the length of \
the arrows.  The number is the factor multiplied by the vector field to produce the \
displacement to the tip of the arrow."));
    SetVar(textBox, WHICHVAR, NewSymbol(VECTORFACTOR));
    SetMethod(textBox, CHANGEDVALUE, EnterRealNumber);
    top -= EDITBOXHEIGHT + MINORBORDER;

    /*Create the shaft width text box*/
    mid = top - EDITBOXHEIGHT / 2;
    textBox = NewTextBox(left, left + ACNAMEWIDTH,
			 mid - TEXTBOXHEIGHT / 2 + EDITBOXDOWN,
			 mid + TEXTBOXHEIGHT / 2 + EDITBOXDOWN,
			 0, "Shaft Radius Text", "Shaft Radius:");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);

    var = GetRealVar("AddArrowControls", arrows, SHAFTWIDTH);
    if (!var)
    {
	return ObjFalse;
    }
    sprintf(tempStr, "%g", GetReal(var));

    textBox = NewTextBox(left + ACNAMEWIDTH + MINORBORDER,
			 left + ACNAMEWIDTH + MINORBORDER + ACTEXTWIDTH,
			 mid - EDITBOXHEIGHT / 2, mid + EDITBOXHEIGHT / 2,
			 EDITABLE + WITH_PIT + ONE_LINE, "Shaft Radius Number", tempStr);
    SetVar(textBox, PARENT, panelContents);
    PrefixList(panelContents, textBox);
    SetTextAlign(textBox, RIGHTALIGN);
    SetVar(textBox, REPOBJ, arrows);
    SetVar(textBox, HELPSTRING, NewString("This text box controls the radius of \
the arrow shaft.  The number is in field coordinates."));
    SetVar(textBox, WHICHVAR, NewSymbol(SHAFTWIDTH));
    SetMethod(textBox, CHANGEDVALUE, EnterRealNumber);
    top -= EDITBOXHEIGHT + MINORBORDER;

    /*Create the head radius text box*/
    mid = top - EDITBOXHEIGHT / 2;
    textBox = NewTextBox(left, left + ACNAMEWIDTH,
			 mid - TEXTBOXHEIGHT / 2 + EDITBOXDOWN,
			 mid + TEXTBOXHEIGHT / 2 + EDITBOXDOWN,
			 0, "Head Radius Text", "Head Radius:");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);

    var = GetRealVar("AddArrowControls", arrows, HEADWIDTH);
    if (!var)
    {
	return ObjFalse;
    }
    sprintf(tempStr, "%g", GetReal(var));

    textBox = NewTextBox(left + ACNAMEWIDTH + MINORBORDER,
			 left + ACNAMEWIDTH + MINORBORDER + ACTEXTWIDTH,
			 mid - EDITBOXHEIGHT / 2, mid + EDITBOXHEIGHT / 2,
			 EDITABLE + WITH_PIT + ONE_LINE, "Head Radius Number", tempStr);
    SetVar(textBox, PARENT, panelContents);
    PrefixList(panelContents, textBox);
    SetTextAlign(textBox, RIGHTALIGN);
    SetVar(textBox, REPOBJ, arrows);
    SetVar(textBox, HELPSTRING, NewString("This text box controls the radius of \
the arrowhead.  The number is in field coordinates.  To make the arrows look like \
pencil shapes, make this number the same as the shaft radius."));
    SetVar(textBox, WHICHVAR, NewSymbol(HEADWIDTH));
    SetMethod(textBox, CHANGEDVALUE, EnterRealNumber);
    top -= EDITBOXHEIGHT + MINORBORDER;

    /*Create the head length text box*/
    mid = top - EDITBOXHEIGHT / 2;
    textBox = NewTextBox(left, left + ACNAMEWIDTH,
			 mid - TEXTBOXHEIGHT / 2 + EDITBOXDOWN,
			 mid + TEXTBOXHEIGHT / 2 + EDITBOXDOWN,
			 0, "Head Length Text", "Head Length:");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);

    var = GetRealVar("AddArrowControls", arrows, HEADLENGTH);
    if (!var)
    {
	return ObjFalse;
    }
    sprintf(tempStr, "%g", GetReal(var));

    textBox = NewTextBox(left + ACNAMEWIDTH + MINORBORDER,
			 left + ACNAMEWIDTH + MINORBORDER + ACTEXTWIDTH,
			 mid - EDITBOXHEIGHT / 2, mid + EDITBOXHEIGHT / 2,
			 EDITABLE + WITH_PIT + ONE_LINE, "Head LENGTH Number", tempStr);
    SetVar(textBox, PARENT, panelContents);
    PrefixList(panelContents, textBox);
    SetTextAlign(textBox, RIGHTALIGN);
    SetVar(textBox, REPOBJ, arrows);
    SetVar(textBox, HELPSTRING, NewString("This text box controls the length of \
the arrowhead.  The number is in field coordinates.  To make the arrows look like \
skinny needles, make this number very large."));
    SetVar(textBox, WHICHVAR, NewSymbol(HEADLENGTH));
    SetMethod(textBox, CHANGEDVALUE, EnterRealNumber);
    top -= EDITBOXHEIGHT + MINORBORDER;

    /*Make the arrows style radio buttons*/
    titleBox = TemplateTitleBox(ArrowsTemplate, "Arrow Style");
    PrefixList(panelContents, titleBox);
    SetVar(titleBox, PARENT, panelContents);

    radio = NewRadioButtonGroup("Arrow Style Radio");
    PrefixList(panelContents, radio);
    SetVar(radio, PARENT, panelContents);

    button = TemplateRadioButton(ArrowsTemplate, "Line");
    AddRadioButton(radio, button);

    button = TemplateRadioButton(ArrowsTemplate, "Wire");
    AddRadioButton(radio, button);

    button = TemplateRadioButton(ArrowsTemplate, "Flat");
    AddRadioButton(radio, button);

    button = TemplateRadioButton(ArrowsTemplate, "Solid");
    AddRadioButton(radio, button);

    var = GetVar(arrows, STYLE);
    if (var)
    {
	style = GetInt(var);
    }
    else
    {
	style = 0;
    }
    SetValue(radio, NewInt(style));
    SetVar(radio, REPOBJ, arrows);
    SetMethod(radio, CHANGEDVALUE, ChangeArrowStyle);

    return ObjTrue;
}

void InitArrows()
/*Initializes the arrows object*/
{
    ObjPtr icon;

    /*Class for an arrows object*/
    arrowsClass = NewObject(visGeometryClass, 0);
    AddToReferenceList(arrowsClass);
    SetVar(arrowsClass, NAME, NewString("Arrows"));
    SetMethod(arrowsClass, INITIALIZE, ArrowsInit);
    SetVar(arrowsClass, DEFAULTICON, icon = NewObject(visIcon, 0));
    SetVar(icon, WHICHICON, NewInt(ICONARROWS));
    SetVar(icon, NAME, NewString("Arrows"));
    SetVar(icon, HELPSTRING,
	NewString("This icon represents an arrows object.  The arrows object \
shows arrows at samples of a vector field with 2 or 3 components."));
    DeclareIndirectDependency(arrowsClass, SURFACE, MAINDATASET, DATA);
    DeclareIndirectDependency(arrowsClass, SURFACE, MAINDATASET, CURDATA);
    DeclareIndirectDependency(arrowsClass, SURFACE, MAINDATASET);
    DeclareDependency(arrowsClass, SURFACE, VECTORFACTOR);
    DeclareDependency(arrowsClass, SURFACE, SHAFTWIDTH);
    DeclareDependency(arrowsClass, SURFACE, HEADWIDTH);
    DeclareDependency(arrowsClass, SURFACE, HEADLENGTH);
    DeclareDependency(arrowsClass, SURFACE, STYLE);
    SetMethod(arrowsClass, SURFACE, MakeArrowsSurface);
    SetMethod(arrowsClass, PICCOLORED, MakeArrowsColored);
    SetMethod(arrowsClass, SETMAINDATASET, SetArrowsMainDataset);
    SetVar(arrowsClass, STYLE, NewInt(0));

    SetMethod(arrowsClass, ADDCONTROLS, AddArrowsControls);
    icon = NewIcon(0, 0, ICONARROWS, "Arrows");
    SetVar(arrowsClass, CONTROLICON, icon);

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

void KillArrows()
/*Kills the arrows*/
{
    DeleteThing(arrowsClass);
}
