/*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));
    }
    return ObjTrue;
}

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 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;
    int nSubdivisions;		/*# of subdivisions*/
    int arrowStyle;		/*Style of arrow*/
    int curCapEnds;		/*Whether or not to cap ends*/

    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);

    {
	int nTraversalDims;
	long *indices;
	long *traversalDims;
	int whichDim;
	int nFormComponents, nDataComponents;

	/*Get some parameters*/
	var = GetIntVar("MakeArrowsSurface", visObject, FRUSTUMSUBDIV);
	if (var)
	{
	    nSubdivisions = GetInt(var);
	}
	else
	{
	    nSubdivisions = 3;
	}
	curCapEnds = GetPredicate(visObject, CAPENDSP);
	var = GetVar(visObject, STYLE);
	if (var)
	{
	    arrowStyle = GetInt(var);
	}
	else
	{
	    arrowStyle = 0;
	}

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

	/*Get the number of components*/
	nFormComponents = GetNComponents(FORMFIELD);
	nDataComponents = GetNComponents(FIELD1);
	
	/*Get the information on traversing the dataset*/
	nTraversalDims = CountTraversalDims(FORMFIELD);
	if (nTraversalDims)
	{
	    traversalDims = (long *) Alloc(sizeof(long) * nTraversalDims);
	    indices = (long *) Alloc(sizeof(long) * nTraversalDims);
	}
	else
	{
	    indices = (long *) Alloc(sizeof(long));
	}
	GetTraversalDims(FORMFIELD, traversalDims);

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

	do
	{
	    real x1, y1, z1, vx, vy, vz, len, nx, ny, nz;

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

	    /*Do nothing for missing data*/
	    if (x1 == missingData ||
		y1 == missingData ||
		z1 == missingData)
	    {
		goto nextOne;
	    }

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

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

	    /*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, nSubdivisions, 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, nSubdivisions, curCapEnds);
		    rad2 = headWidth;
		    end1[0] = x1 + vx;
		    end1[1] = y1 + vy;
		    end1[2] = z1 + vz;
		    rad1 = 0.0;
		    ConvertFrustumOntoPicture(picture, end1, rad1, end2, rad2, nSubdivisions, curCapEnds);
		}
		break;
	    }

nextOne:
	    /*Advance to next arrow*/
	    for (whichDim = 0; whichDim < nTraversalDims; ++whichDim)
	    {
		if (traversalDims[whichDim] > 0)
		{
		    if ((++indices[whichDim]) >= traversalDims[whichDim])
		    {
			indices[whichDim] = 0;
		    }
		    else
		    {
			break;
		    }
		}
	    }
	} while (whichDim < nTraversalDims); /*Break is based on advance*/
	SAFEFREE(traversalDims);
	SAFEFREE(indices);
    }

    SetVar(visObject, SURFACE, picture);
    SetVar(picture, REPOBJ, visObject);
    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;
    }

    textBox = NewTextBox(left + ACNAMEWIDTH + MINORBORDER,
			 left + ACNAMEWIDTH + MINORBORDER + ACTEXTWIDTH,
			 mid - EDITBOXHEIGHT / 2, mid + EDITBOXHEIGHT / 2,
			 EDITABLE + WITH_PIT + ONE_LINE, "Arrow Length Number", "");
    SetVar(textBox, PARENT, panelContents);
    PrefixList(panelContents, textBox);
    SetTextAlign(textBox, RIGHTALIGN);
    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."));
    AssocTextRealControlWithVar(textBox, arrows, VECTORFACTOR, minusInf, plusInf, TR_NE_TOP | TR_NE_BOTTOM);
    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;
    }

    textBox = NewTextBox(left + ACNAMEWIDTH + MINORBORDER,
			 left + ACNAMEWIDTH + MINORBORDER + ACTEXTWIDTH,
			 mid - EDITBOXHEIGHT / 2, mid + EDITBOXHEIGHT / 2,
			 EDITABLE + WITH_PIT + ONE_LINE, "Shaft Radius Number", "");
    SetVar(textBox, PARENT, panelContents);
    PrefixList(panelContents, textBox);
    SetTextAlign(textBox, RIGHTALIGN);
    SetVar(textBox, HELPSTRING, NewString("This text box controls the radius of \
the arrow shaft.  The number is in field coordinates."));
    AssocTextRealControlWithVar(textBox, arrows, SHAFTWIDTH, minusInf, plusInf, TR_NE_TOP | TR_NE_BOTTOM);
    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;
    }

    textBox = NewTextBox(left + ACNAMEWIDTH + MINORBORDER,
			 left + ACNAMEWIDTH + MINORBORDER + ACTEXTWIDTH,
			 mid - EDITBOXHEIGHT / 2, mid + EDITBOXHEIGHT / 2,
			 EDITABLE + WITH_PIT + ONE_LINE, "Head Radius Number", "");
    SetVar(textBox, PARENT, panelContents);
    PrefixList(panelContents, textBox);
    SetTextAlign(textBox, RIGHTALIGN);
    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."));
    AssocTextRealControlWithVar(textBox, arrows, HEADWIDTH, minusInf, plusInf, TR_NE_TOP | TR_NE_BOTTOM);
    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;
    }

    textBox = NewTextBox(left + ACNAMEWIDTH + MINORBORDER,
			 left + ACNAMEWIDTH + MINORBORDER + ACTEXTWIDTH,
			 mid - EDITBOXHEIGHT / 2, mid + EDITBOXHEIGHT / 2,
			 EDITABLE + WITH_PIT + ONE_LINE, "Head Length Number", "");
    SetVar(textBox, PARENT, panelContents);
    PrefixList(panelContents, textBox);
    SetTextAlign(textBox, RIGHTALIGN);
    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 infinity."));
    AssocTextRealControlWithVar(textBox, arrows, HEADLENGTH, minusInf, plusInf, 0);
    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);
    SetVar(button, HELPSTRING, NewString("When this button is on, the \
arrows will be drawn as a single line.  This is the fastest way of drawing \
arrows."));

    button = TemplateRadioButton(ArrowsTemplate, "Wire");
    AddRadioButton(radio, button);
    SetVar(button, HELPSTRING, NewString("When this button is on, the \
arrows will be drawn in wire frame."));

    button = TemplateRadioButton(ArrowsTemplate, "Flat");
    AddRadioButton(radio, button);
    SetVar(button, HELPSTRING, NewString("When this button is on, the \
arrows will be drawn as flat, shaded, 2-D arrows."));

    button = TemplateRadioButton(ArrowsTemplate, "Solid");
    AddRadioButton(radio, button);
    SetVar(button, HELPSTRING, NewString("When this button is on, the \
arrows will be drawn as solid, shaded, cylindrical 3-D arrows.."));

    var = GetVar(arrows, STYLE);
    if (!var)
    {
	SetVar(arrows, STYLE, NewInt(0));
    }
    AssocDirectControlWithVar(radio, arrows, STYLE);
    SetVar(radio, HELPSTRING, NewString("These radio buttons control the \
style of arrows display used.  Press a button to change the style."));

    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, CHANGED);
    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);
}
