/*ScianVisBalls.c
  Sept 23, 1992
  Eric Pepke
  Balls visualization object in SciAn*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianArrays.h"
#include "ScianWindows.h"
#include "ScianTextBoxes.h"
#include "ScianButtons.h"
#include "ScianTitleBoxes.h"
#include "ScianObjWindows.h"
#include "ScianIcons.h"
#include "ScianColors.h"
#include "ScianControls.h"
#include "ScianLists.h"
#include "ScianSliders.h"
#include "ScianIDs.h"
#include "ScianDatasets.h"
#include "ScianPictures.h"
#include "ScianDialogs.h"
#include "ScianErrors.h"
#include "ScianComplexControls.h"
#include "ScianMethods.h"
#include "ScianStyle.h"
#include "ScianVisObjects.h"
#include "ScianVisBalls.h"
#include "ScianDraw.h"
#include "ScianTemplates.h"
#include "ScianTemplateHelper.h"
#include "ScianSymbols.h"

ObjPtr visSized;	/*Class for sizable object*/
ObjPtr visBalls;	/*Class for ball cloud*/

/*Ball style flags*/
#define BS_JACKS	2
#define BS_CUBES	4
#define BS_OCTOHEDRA	8
#define BS_SPHERES	16

/*Just in case I screw up*/
#undef DEFORMOBJ
#undef DEFCONSTANT
#undef DEFOFFSET
#undef DEFFACTOR
#undef DEFORMSWITCH
#define visDeformed HAHAHAHA

void CannotSizeByGeometry()
{
    WinInfoPtr errWindow;
    errWindow = AlertUser(UIERRORALERT, (WinInfoPtr) 0,
	"A geometry set cannot be used to size a visualization object.",
        0, 0, "Oh well");
    SetVar((ObjPtr) errWindow, HELPSTRING,
        NewString("You have tried to size a visualization object with a \
geomety object.  This will not work."));
}

static ObjPtr ChangeSSValue(object)
ObjPtr object;
/*Changes the value of size switch*/
{
    ObjPtr var;
    int value;
    ObjPtr repObj;

    /*Get the value for comparison*/
    var = GetIntVar("ChangeSSValue", object, VALUE);
    if (!var) return ObjFalse;
    value = GetInt(var);

    /*Get the repobj*/
    repObj = GetObjectVar("ChangeSSValue", object, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    if (value)
    {
	/*Switch to sized by object*/
	SetVar(repObj, SIZESWITCH, NewInt(1));
    }
    else
    {
	/*Switch to null color*/
	SetVar(repObj, SIZESWITCH, NewInt(0));
    }

    ImInvalid(repObj);
    return ObjTrue;
}

static ObjPtr DropInSizeCorral(corral, object, x, y)
ObjPtr corral, object;
int x, y;
/*Drops an icon in a size corral*/
{
    ObjPtr visObj;
    ObjPtr fieldObj;
    ObjPtr icon;
    ObjPtr name;
    ObjPtr defaultIcon;
    ObjPtr contents;
    ObjPtr button;
    long info;

    /*Find the visualization object*/
    visObj = GetObjectVar("DropInSizeCorral", corral, REPOBJ);
    if (!visObj)
    {
	return ObjFalse;
    }

    /*Get the field object*/
    fieldObj = GetObjectVar("DropInSizeCorral", object, REPOBJ);
    if (!fieldObj)
    {
	return ObjFalse;
    }

    info = GetDatasetInfo(fieldObj);
    if ((info & DS_HASGEOMETRY))
    {
	DoTask(CannotSizeByGeometry);
	return ObjFalse;
    }

    /*Create an icon for it*/
    name = GetStringVar("DropInSizeCorral", fieldObj, NAME);
    if (!name)
    {
	return ObjFalse;
    }

    defaultIcon = GetVar(fieldObj, DEFAULTICON);
    if (defaultIcon)
    {
	ObjPtr locArray;
	real loc[2];
	icon = NewObject(defaultIcon, 0);
	SetVar(icon, NAME, name);
	loc[0] = x;
	loc[1] = y;
	locArray = NewRealArray(1, 2L);
	CArray2Array(locArray, loc);
	SetVar(icon, ICONLOC, locArray);
    }
    else
    {
	icon = NewIcon(x, y, ICONQUESTION, GetString(name));
    }
    
    /*Make the icon ball to the field*/
    SetVar(icon, REPOBJ, fieldObj);

    /*Make it the only icon in the corral*/
    contents = NewList();
    PrefixList(contents, icon);
    SetVar(corral, CONTENTS, contents);
    SetVar(contents, PARENT, corral);
    SetVar(icon, PARENT, corral);
    RecalcScroll(corral);

    /*Make this object the sized object*/
    SetVar(visObj, SIZEOBJ, fieldObj);

    ImInvalid(visObj);
    ImInvalid(corral);
    return ObjTrue;
}

static ObjPtr AddSizedControls(object, panelContents)
ObjPtr object, panelContents;
/*Adds controls to a sized object*/
{
    ObjPtr control, button, checkBox, corral, sw, textBox, slider, icon, radioGroup;
    ObjPtr sizeObj, titleBox;
    ObjPtr var;
    int width, left, right, top, bottom, cellHeight, m1, m2;
    real initValue;
    real baseColor[3];
    real hs[2], dummy;
    ObjPtr parent;

    /*Get the parent*/
    parent = GetObjectVar("AddSizedControls", panelContents, PARENT);

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

    cellHeight = ONECORRALHEIGHT;

    /*Precalculate the midlines for convenience*/
    m1 = CWINHEIGHT - MAJORBORDER - cellHeight / 2;
    m1 += MINORBORDER;
    m2 = m1 - cellHeight - MAJORBORDER - TEXTBOXHEIGHT - TEXTBOXSEP;

    /*Create the size source corral*/
    corral = NewIconCorral(NULLOBJ,
			   left, left + ONECORRALWIDTH,
			   m2 - ONECORRALHEIGHT / 2,
			   m2 + ONECORRALHEIGHT / 2, 0);
    SetVar(corral, SINGLECORRAL, ObjTrue);
    SetVar(corral, TOPDOWN, ObjTrue);
    SetVar(corral, NAME, NewString("Size Field"));
    PrefixList(panelContents, corral);
    SetVar(corral, HELPSTRING,
	NewString("This corral shows the dataset that is used to \
determine the size of the visualization object.  \
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, DropInSizeCorral);

    /*Create the size source text box*/
    textBox = NewTextBox(left, left + ONECORRALWIDTH, 
			 m2 - ONECORRALHEIGHT / 2 - TEXTBOXSEP - TEXTBOXHEIGHT,
			 m2 - ONECORRALHEIGHT / 2 - TEXTBOXSEP,
			 0, "Size Field Text", "Size Field");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetTextAlign(textBox, CENTERALIGN);

    left += ONECORRALWIDTH + MINORBORDER;
    sizeObj = GetVar(object, SIZEOBJ);
    if (sizeObj)
    {
	ObjPtr name, defaultIcon;
	/*Drop icon in corral, if need be*/
	name = GetVar(sizeObj, NAME);
	defaultIcon = GetVar(sizeObj, DEFAULTICON);
	if (defaultIcon)
	{
	    icon = NewObject(defaultIcon, 0);
	    SetVar(icon, NAME, name);
	}
	else
	{
	    icon = NewIcon(0, 0, ICONQUESTION, GetString(name));
        }
	SetVar(icon, REPOBJ, sizeObj);
	SetVar(icon, ICONLOC, NULLOBJ);
        DropIconInCorral(corral, icon);
    }

    /*Create the constant size control*/
    var = GetVar(object, SIZECONSTANT);
    if (var)
    {
	PrintNumber(tempStr, GetReal(var));
    }
    else
    {
	PrintNumber(tempStr, 0.0);;
    }
    textBox = NewTextBox(MAJORBORDER, left - MINORBORDER, 
			m1 - EDITBOXHEIGHT / 2,
			m1 + EDITBOXHEIGHT / 2, 
			EDITABLE + WITH_PIT + ONE_LINE,
			"Fixed Size", tempStr);
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetVar(textBox, REPOBJ, object);
    SetVar(textBox, HELPSTRING, NewString("This text box gives a constant \
size when it is selected by the switch."));
    SetVar(textBox, WHICHVAR, NewSymbol(SIZECONSTANT));
    SetTextAlign(textBox, RIGHTALIGN);
    SetMethod(textBox, CHANGEDVALUE, EnterRealNumber);

    /*Create the text box*/
    textBox = NewTextBox(MAJORBORDER - 20, left - MINORBORDER + 20, 
			 m1 - EDITBOXHEIGHT / 2 - TEXTBOXSEP - 2 * TEXTBOXHEIGHT,
			 m1 - EDITBOXHEIGHT / 2 - TEXTBOXSEP,
			 0, "Fixed Size Text", "Fixed Size");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetTextAlign(textBox, CENTERALIGN);
    SetVar(textBox, REPOBJ, object);

    /*Create the choice switch*/
    var = GetVar(object, SIZESWITCH);
    sw = NewSwitch(left, left + SWITCHWIDTH,
			m2 - cellHeight / 2 - (MAJORBORDER + TEXTBOXHEIGHT + TEXTBOXSEP) / 2,
			m1 + cellHeight / 2 + (MAJORBORDER + TEXTBOXHEIGHT + TEXTBOXSEP) / 2,
			2, 0, var ? GetInt(var) : 0,
			"Size Switch");
    PrefixList(panelContents, sw);
    SetVar(sw, PARENT, panelContents);
    SetVar(sw, REPOBJ, object);
    SetVar(sw, HELPSTRING, NewString("This switch controls whether the \
soze of the visualization object comes from a \
field or from a fixed size."));
    SetMethod(sw, CHANGEDVALUE, ChangeSSValue);

    left += SWITCHWIDTH + MINORBORDER;

    /*Create the * text box*/
    right = left + MINORBORDER;
    textBox = NewTextBox(left, right + MINORBORDER, 
			m1 - EDITBOXHEIGHT / 2,
			m1 + EDITBOXHEIGHT / 2, 
			0,
			"Splat", "*");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetTextFont(textBox, "Courier-Bold");
    SetTextSize(textBox, 18);
    left = right + MINORBORDER;

    /*Create the size factor*/
    var = GetVar(object, SIZEFACTOR);
    if (var)
    {
	PrintNumber(tempStr, GetReal(var));
    }
    else
    {
	PrintNumber(tempStr, 0.0);
    }
    right = left + SIZEEDITWIDTH;
    textBox = TemplateTextBox(VisSizeTemplate, "Size Factor", 
			EDITABLE + WITH_PIT + ONE_LINE,
			tempStr);
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetVar(textBox, REPOBJ, object);
    SetVar(textBox, HELPSTRING, NewString("This text box gives a multiplier \
for the size provided by the switch."));
    SetVar(textBox, WHICHVAR, NewSymbol(SIZEFACTOR));
    SetMethod(textBox, CHANGEDVALUE, EnterRealNumber);
    SetTextAlign(textBox, RIGHTALIGN);

    /*Create the text box*/
    textBox = TemplateTextBox(VisSizeTemplate, "Factor Text",
			0, "Factor");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetTextAlign(textBox, CENTERALIGN);
    SetVar(textBox, REPOBJ, object);
    left = right + MINORBORDER;

    /*Create the + text box*/
    right = left + MINORBORDER;
    textBox = TemplateTextBox(VisSizeTemplate, "Plus", 0, "+");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetTextFont(textBox, "Courier-Bold");
    SetTextSize(textBox, 18);
    left = right + MINORBORDER;

    /*Create the size offset*/
    var = GetVar(object, SIZEOFFSET);
    if (var)
    {
	PrintNumber(tempStr, GetReal(var));
    }
    else
    {
	PrintNumber(tempStr, 0.0);
    }
    right = left + SIZEEDITWIDTH;
    textBox = TemplateTextBox(VisSizeTemplate, "Size Offset", 
			EDITABLE + WITH_PIT + ONE_LINE, tempStr);
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetVar(textBox, REPOBJ, object);
    SetVar(textBox, HELPSTRING, NewString("This text box gives an offset \
for the size provided by the switch, multiplied by the multiplier."));
    SetVar(textBox, WHICHVAR, NewSymbol(SIZEOFFSET));
    SetMethod(textBox, CHANGEDVALUE, EnterRealNumber);
    SetTextAlign(textBox, RIGHTALIGN);

    /*Create the text box*/
    textBox = TemplateTextBox(VisSizeTemplate, "Offset Text", 0, "Offset");
    PrefixList(panelContents, textBox);
    SetVar(textBox, PARENT, panelContents);
    SetTextAlign(textBox, CENTERALIGN);
    SetVar(textBox, REPOBJ, object);
    left = right + MINORBORDER;

    return ObjTrue;
}

static ObjPtr ChangeballStyle(object)
ObjPtr object;
/*Changed value for a ball style flags check box*/
{
    int flagOn;
    ObjPtr val;
    ObjPtr repObj;
    ObjPtr flagsObj, whichFlagObj;
    int flags, whichFlag;

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

    flagsObj = GetVar(repObj, POINTSTYLE);
    if (flagsObj)
    {
	flags = GetInt(flagsObj);
    }
    else
    {
	flags = 0;
    }

    val = GetVar(object, VALUE);
    if (val)
    {
	flagOn = GetInt(val);
    }
    else
    {
	flagOn = false;
    }

    whichFlagObj = GetIntVar("ChangeballStyle", object, WHICHFLAG);
    if (whichFlagObj)
    {
	whichFlag = GetInt(whichFlagObj);
    }
    else
    {
	return ObjFalse;
    }

    if (flagOn)
    {
	flags |= whichFlag;
    }
    else
    {
	flags &= ~whichFlag;
    }

    SetVar(repObj, POINTSTYLE, NewInt(flags));
    ImInvalid(repObj);

    return ObjTrue;
}

static ObjPtr AddBallsControls(object, panelContents)
ObjPtr object, panelContents;
/*Adds controls to a balls object*/
{
    ObjPtr titleBox, checkBox;
    ObjPtr var;
    int style;

    /*Get the style variable*/
    var = GetVar(object, POINTSTYLE);
    if (var)
    {
	style = GetInt(var);
    }
    else
    {
	style = BS_OCTOHEDRA;
    }

    /*Make the title box*/
    titleBox = TemplateTitleBox(BallsTemplate, "Style");
    PrefixList(panelContents, titleBox);
    SetVar(titleBox, PARENT, panelContents);

    /*Make the check boxes*/

#if 0
    checkBox = TemplateCheckBox(BallsTemplate, "Balls", style & PS_POINTS ? 1 : 0);
    PrefixList(panelContents, checkBox);
    SetVar(checkBox, PARENT, panelContents);
    SetVar(checkBox, WHICHFLAG, NewInt(PS_POINTS));
    SetVar(checkBox, REPOBJ, object);
    SetMethod(checkBox, CHANGEDVALUE, ChangeballStyle);
    SetVar(checkBox, HELPSTRING,
	NewString("When this box is on, square balls are drawn at all the nodes \
in the visualization."));
    SetMethod(checkBox, CHANGEDVALUE, ChangeballStyle);
#endif

    checkBox = TemplateCheckBox(BallsTemplate, "Jacks", style & BS_JACKS ? 1 : 0);
    PrefixList(panelContents, checkBox);
    SetVar(checkBox, PARENT, panelContents);
    SetVar(checkBox, WHICHFLAG, NewInt(BS_JACKS));
    SetVar(checkBox, REPOBJ, object);
    SetMethod(checkBox, CHANGEDVALUE, ChangeballStyle);
    SetVar(checkBox, HELPSTRING,
	NewString("When this box is on, jacks are drawn at all the nodes \
in the visualization.  The size of the jacks can be controlled from within \
the Size control panel."));
    SetMethod(checkBox, CHANGEDVALUE, ChangeballStyle);

    checkBox = TemplateCheckBox(BallsTemplate, "Cubes", style & BS_CUBES ? 1 : 0);
    PrefixList(panelContents, checkBox);
    SetVar(checkBox, PARENT, panelContents);
    SetVar(checkBox, WHICHFLAG, NewInt(BS_CUBES));
    SetVar(checkBox, REPOBJ, object);
    SetMethod(checkBox, CHANGEDVALUE, ChangeballStyle);
    SetVar(checkBox, HELPSTRING,
	NewString("When this box is on, cubes are drawn at all the nodes \
in the visualization.  The size of the cubes can be controlled from within \
the Size control panel."));
    SetMethod(checkBox, CHANGEDVALUE, ChangeballStyle);

    checkBox = TemplateCheckBox(BallsTemplate, "Octohedra", style & BS_OCTOHEDRA ? 1 : 0);
    PrefixList(panelContents, checkBox);
    SetVar(checkBox, PARENT, panelContents);
    SetVar(checkBox, WHICHFLAG, NewInt(BS_OCTOHEDRA));
    SetVar(checkBox, REPOBJ, object);
    SetMethod(checkBox, CHANGEDVALUE, ChangeballStyle);
    SetVar(checkBox, HELPSTRING,
	NewString("When this box is on, octohedra are drawn at all the nodes \
in the visualization.  The size of the octohedra can be controlled from within \
the Size control panel."));
    SetMethod(checkBox, CHANGEDVALUE, ChangeballStyle);

    checkBox = TemplateCheckBox(BallsTemplate, "Spheres", style & BS_SPHERES ? 1 : 0);
    PrefixList(panelContents, checkBox);
    SetVar(checkBox, PARENT, panelContents);
    SetVar(checkBox, WHICHFLAG, NewInt(BS_SPHERES));
    SetVar(checkBox, REPOBJ, object);
    SetMethod(checkBox, CHANGEDVALUE, ChangeballStyle);
    SetVar(checkBox, HELPSTRING,
	NewString("When this box is on, spheres are drawn at all the nodes \
in the visualization.  The size of the spheres can be controlled from within \
the Size control panel.  The resolution of the spheres can be controlled from \
within the Geometry control panel."));
    SetMethod(checkBox, CHANGEDVALUE, ChangeballStyle);

    return ObjTrue;
}

typedef VertexPtr SixVertices[6];
typedef VertexPtr CubeVertices[2][2][2];

static ObjPtr MakeBallsSurface(balls)
ObjPtr balls;
/*Makes the surface of a balls object*/
{
    ObjPtr ballsData;
    ObjPtr surface;
    ObjPtr var;
    int style;			/*Style of balls to make*/
    var = GetVar(balls, POINTSTYLE);
    if (var)
    {
	style = GetInt(var);
    }
    else
    {
	style = BS_OCTOHEDRA;
    }

    surface = NewPicture();

    /*Get the main dataset*/
    MakeVar(balls, POINTSFIELD);
    ballsData = GetVar(balls, POINTSFIELD);

    if (ballsData && style)
    {
	/*There is some data.  Put stuff on the surface*/
	int nTraversalDims;		/*Number of dimensions to traverse*/
	int whichDim;			/*Dimension counter*/
	long *traversalDims = 0;	/*Dimensions of the dataset to traverse*/
	long *index = 0;		/*Counting index*/
	long nNodes;			/*# Total nodes in the dataset*/
	VertexPtr *centerVertices = 0;	/*Center vertices*/
	SixVertices *faceVertices = 0;	/*Face-centered vertices*/
	CubeVertices *cornerVertices = 0;  /*Corner vertices*/
	long nVertices;			/*Number of vertices so far*/
	int nComponents;		/*# components of data form*/
	real size = 1.0;		/*Size of the object*/
	VertexPtr tv;			/*Temporary vertex*/
	long k;				/*Counter*/
	real x, y, z;
	PicItemPtr item;
	int sphereSub;			/*# subdivisions in field*/
	ObjPtr sizeObj;			/*Object to size by*/
	real offset, factor;		/*Offset and factor for deformation*/
	Bool sameForm;			/*True iff forms are the same*/

	var = GetIntVar("MakeBallsSurface", balls, SPHERESUBDIV);
	if (var)
	{
	    sphereSub = GetInt(var);
	}
	else
	{
	    sphereSub = 2;
	}

	/*See if we need to size by an object*/
	size = 1.0;
	sizeObj = NULLOBJ;
	if (style & (BS_JACKS | BS_CUBES | BS_OCTOHEDRA | BS_SPHERES))
	{
	    if (GetPredicate(balls, SIZESWITCH))
	    {
		MakeVar(balls, SIZEOBJ);
		sizeObj = GetVar(balls, SIZEOBJ);
	    }
	}

	MakeVar(balls, SIZEFACTOR);
	var = GetRealVar("MakeBallsSurface", balls, SIZEFACTOR);
	if (var)
	{
	    factor = GetReal(var);
	}
	else
	{
	    factor = 1.0;
	}

	MakeVar(balls, SIZEOFFSET);
	var = GetRealVar("MakeBallsSurface", balls, SIZEOFFSET);
	if (var)
	{
	    offset = GetReal(var);
	}
	else
	{
	    offset = 0.0;
	}

	SetCurForm(FORMFIELD, ballsData);
	nComponents = GetNComponents(FORMFIELD);

	if (sizeObj)
	{
	    SetCurField(FIELD1, sizeObj);
	    SetCurForm(FIELD2, sizeObj);
	    if (IdenticalFields(FORMFIELD, FIELD2))
	    {
		sameForm = true;
	    }
	    else
	    {
		sameForm = false;
	    }
	}
	else
	{
	    MakeVar(balls, SIZECONSTANT);
	    var = GetRealVar("MakeBallsSurface", balls, SIZECONSTANT);
	    if (var)
	    {
		size = GetReal(var) * factor + offset;
	    }
	    else
	    {
		size = factor + offset;
	    }
	}

	/*Get the information on traversing the dataset*/
	nTraversalDims = CountTraversalDims(FORMFIELD);
	if (nTraversalDims)
	{
	    traversalDims = (long *) malloc(sizeof(long) * nTraversalDims);
	    index = (long *) malloc(sizeof(long) * nTraversalDims);
	}
	else
	{
	    index = (long *) malloc(sizeof(long));
	}
	GetTraversalDims(FORMFIELD, traversalDims);

	/*Calculate the number of nodes*/
	nNodes = 1;
	for (whichDim = 0; whichDim < nTraversalDims; ++whichDim)
	{
	    if (traversalDims[whichDim] > 0)
	    {
		nNodes *= traversalDims[whichDim];
	    }
	}

	/*Make as many vertices as we might need nodes*/
	centerVertices = (VertexPtr *) malloc(sizeof(VertexPtr) * nNodes);
	if (!centerVertices)
	{
	    OMErr();
	    return ObjFalse;
	}
	if ((style & BS_JACKS) || (style & BS_OCTOHEDRA))
	{
	    faceVertices = (SixVertices *) malloc(sizeof(SixVertices) * nNodes);
	    if (!faceVertices)
	    {
		OMErr();
		SAFEFREE(centerVertices);
		return ObjFalse;
	    }
	}
	if ((style & BS_CUBES))
	{
	    cornerVertices = (CubeVertices *) malloc(sizeof(CubeVertices) * nNodes);
	    if (!cornerVertices)
	    {
		OMErr();
		SAFEFREE(centerVertices);
		SAFEFREE(cornerVertices);
		return ObjFalse;
	    }
	}
	
	/*Fill the vertices*/

	nVertices = 0;

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

	/*Traverse all the balls*/
	do
	{
	    /*Sample the location at a ball*/
	    x = (0 >= nComponents) ? 0.0 : SelectFieldComponent(FORMFIELD, 0, index);
	    y = (1 >= nComponents) ? 0.0 : SelectFieldComponent(FORMFIELD, 1, index);
	    z = (2 >= nComponents) ? 0.0 : SelectFieldComponent(FORMFIELD, 2, index);
	    if (x == missingData) x = 0.0;
	    if (y == missingData) y = 0.0;
	    if (z == missingData) z = 0.0;

	    /*If there's a size object, make a new size*/
	    if (sizeObj)
	    {
		if (sameForm)
		{
		    size = SelectFieldScalar(FIELD1, index) * factor + offset;
		}
		else
		{
		    real sample;
		    real position[3];
		    position[0] = x; position[1] = y; position[2] = z;
		    sample = SampleSpatScalar(FIELD1, FIELD2,
				    3, position, true);
		    size = sample * factor + offset;
		}
	    }

	    /*Make a vertex*/
	    if (nVertices)
	    {
		centerVertices[nVertices] = NewVertex(surface, VF_NEXTCANON);
	    }
	    else
	    {
		centerVertices[nVertices] = NewVertex(surface, VF_FIRSTCANON);		
	    }

	    /*Make some center vertices*/
	    centerVertices[nVertices] -> position[0] = x;
	    centerVertices[nVertices] -> position[1] = y;
	    centerVertices[nVertices] -> position[2] = z;
	    centerVertices[nVertices] -> normal[0] = 0.0;
	    centerVertices[nVertices] -> normal[0] = 0.0;
	    centerVertices[nVertices] -> normal[0] = 1.0;
	    centerVertices[nVertices] -> colorIndex = 2;

	    /*Put in the sphere if need be*/
	    if ((style & BS_SPHERES))
	    {
		ConvertSphereOntoPicture(surface, centerVertices[nVertices], size,
			VF_SAMEPLACE, sphereSub);
	    }

	    if ((style & BS_JACKS) || (style & BS_OCTOHEDRA))
	    {
		/*Make some face centered vertices*/
		faceVertices[nVertices][0] = tv = NewVertex(surface, VF_SAMEPLACE);
		tv -> position[0] = x - size;
		tv -> position[1] = y;
		tv -> position[2] = z;
		tv -> normal[0] = -1.0;
		tv -> normal[1] = 0.0;
		tv -> normal[2] = 0.0;

		faceVertices[nVertices][1] = tv = NewVertex(surface, VF_SAMEPLACE);
		tv -> position[0] = x + size;
		tv -> position[1] = y;
		tv -> position[2] = z;
		tv -> normal[0] = 1.0;
		tv -> normal[1] = 0.0;
		tv -> normal[2] = 0.0;

		faceVertices[nVertices][2] = tv = NewVertex(surface, VF_SAMEPLACE);
		tv -> position[0] = x;
		tv -> position[1] = y - size;
		tv -> position[2] = z;
		tv -> normal[0] = 0.0;
		tv -> normal[1] = -1.0;
		tv -> normal[2] = 0.0;

		faceVertices[nVertices][3] = tv = NewVertex(surface, VF_SAMEPLACE);
		tv -> position[0] = x;
		tv -> position[1] = y + size;
		tv -> position[2] = z;
		tv -> normal[0] = 0.0;
		tv -> normal[1] = 1.0;
		tv -> normal[2] = 0.0;

		faceVertices[nVertices][4] = tv = NewVertex(surface, VF_SAMEPLACE);
		tv -> position[0] = x;
		tv -> position[1] = y;
		tv -> position[2] = z - size;
		tv -> normal[0] = 0.0;
		tv -> normal[1] = 0.0;
		tv -> normal[2] = -1.0;

		faceVertices[nVertices][5] = tv = NewVertex(surface, VF_SAMEPLACE);
		tv -> position[0] = x;
		tv -> position[1] = y;
		tv -> position[2] = z + size;
		tv -> normal[0] = 0.0;
		tv -> normal[1] = 0.0;
		tv -> normal[2] = 1.0;
	    }

#undef MAKECORNERVERTEX
#define MAKECORNERVERTEX(a, b, c)					\
		cornerVertices[nVertices][a][b][c] = tv =		\
			NewVertex(surface, VF_SAMEPLACE);		\
		tv -> position[0] = a ? x + size : x - size;		\
		tv -> position[1] = b ? y + size : y - size;		\
		tv -> position[2] = c ? z + size : z - size;		\
		tv -> normal[0] = a ? SQ33 : -SQ33;			\
		tv -> normal[1] = b ? SQ33 : -SQ33;			\
		tv -> normal[2] = c ? SQ33 : -SQ33;

	    if ((style & BS_CUBES))
	    {
		/*Make some corner vertices*/
		MAKECORNERVERTEX(0, 0, 0);
		MAKECORNERVERTEX(0, 0, 1);
		MAKECORNERVERTEX(0, 1, 0);
		MAKECORNERVERTEX(0, 1, 1);
		MAKECORNERVERTEX(1, 0, 0);
		MAKECORNERVERTEX(1, 0, 1);
		MAKECORNERVERTEX(1, 1, 0);
		MAKECORNERVERTEX(1, 1, 1);
	    }
#undef MAKECORNERVERTEX

	    ++nVertices;

	    /*Advance to next ball*/
	    for (whichDim = 0; whichDim < nTraversalDims; ++whichDim)
	    {
		if (traversalDims[whichDim] > 0)
		{
		    if ((++index[whichDim]) >= traversalDims[whichDim])
		    {
			index[whichDim] = 0;
		    }
		    else
		    {
			break;
		    }
		}
	    }
	} while (whichDim < nTraversalDims); /*Break is based on advance*/
	
	if (style & BS_JACKS)
	{
	    /*Make a jack*/
	    /*DIKEO do something about line width*/
	    for (k = 0; k < nVertices; ++k)
	    {
		AppendSPolylineToPicture(surface, 1, 0, 2, &(faceVertices[k][0]));
		AppendSPolylineToPicture(surface, 1, 0, 2, &(faceVertices[k][2]));
		AppendSPolylineToPicture(surface, 1, 0, 2, &(faceVertices[k][4]));
	    }
	}

#undef MAKETRI
#define MAKETRI(a, b, c)			\
	v[0] = faceVertices[k][a];		\
	v[1] = faceVertices[k][b];		\
	v[2] = faceVertices[k][c];		\
	    AppendSPolyToPolys(polys, 3, v);

	if (style & BS_OCTOHEDRA)
	{
	    /*Make some octohedra*/
	    PolysPtr polys;
	    VertexPtr v[3];		/*Vertices of triangles*/

	    for (k = 0; k < nVertices; ++k)
	    {
		polys = AppendPolysToPicture(surface);
		polys -> enclosed = true;
    
		MAKETRI(0, 5, 3);
		MAKETRI(3, 5, 1);
		MAKETRI(1, 5, 2);
		MAKETRI(2, 5, 0);
		MAKETRI(0, 4, 2);
		MAKETRI(2, 4, 1);
		MAKETRI(1, 4, 3);
		MAKETRI(3, 4, 0);
	    }
	}
#undef MAKETRI

	if (style & BS_CUBES)
	{
	    /*Make some cubes*/
	    PolysPtr polys;
	    VertexPtr v[4];		/*Vertices of triangles*/

	    for (k = 0; k < nVertices; ++k)
	    {
		polys = AppendPolysToPicture(surface);
		/*DIKEO not quite right*/

		v[0] = cornerVertices[k][0][0][0];
		v[1] = cornerVertices[k][1][0][0];
		v[2] = cornerVertices[k][1][1][0];
		v[3] = cornerVertices[k][0][1][0];
		AppendSPolyToPolys(polys, 4, v);

		v[0] = cornerVertices[k][0][0][1];
		v[1] = cornerVertices[k][0][1][1];
		v[2] = cornerVertices[k][1][1][1];
		v[3] = cornerVertices[k][1][0][1];
		AppendSPolyToPolys(polys, 4, v);

		v[0] = cornerVertices[k][0][0][0];
		v[1] = cornerVertices[k][0][0][1];
		v[2] = cornerVertices[k][1][0][1];
		v[3] = cornerVertices[k][1][0][0];
		AppendSPolyToPolys(polys, 4, v);

		v[0] = cornerVertices[k][0][1][0];
		v[1] = cornerVertices[k][1][1][0];
		v[2] = cornerVertices[k][1][1][1];
		v[3] = cornerVertices[k][0][1][1];
		AppendSPolyToPolys(polys, 4, v);

		v[0] = cornerVertices[k][0][0][0];
		v[1] = cornerVertices[k][0][0][1];
		v[2] = cornerVertices[k][0][1][1];
		v[3] = cornerVertices[k][0][1][0];
		AppendSPolyToPolys(polys, 4, v);

		v[0] = cornerVertices[k][1][0][0];
		v[1] = cornerVertices[k][1][1][0];
		v[2] = cornerVertices[k][1][1][1];
		v[3] = cornerVertices[k][1][0][1];
		AppendSPolyToPolys(polys, 4, v);

	    }
	}

	SetVar(surface, MAINDATASET, ballsData);

	/*Free up temporary storage*/
	SAFEFREE(traversalDims);
	SAFEFREE(index);
	SAFEFREE(centerVertices);
	SAFEFREE(faceVertices);
	SAFEFREE(cornerVertices);
    }

    SetVar(balls, SURFACE, surface);
    SetVar(surface, REPOBJ, balls);
    return ObjTrue;
}

static ObjPtr BallsInit(balls)
ObjPtr balls;
/*Initializes a balls object*/
{
    ObjPtr colorObj;
    ObjPtr ballsField;
    ObjPtr sizeObj;

    MakeVar(balls, POINTSFIELD);
    SetVar(balls, COLOROBJ, colorObj = GetVar(balls, POINTSFIELD));

    if (colorObj)
    {
	SetVar(balls, COLORS, ObjTrue);

	/*See if there is a size object*/
	MakeVar(colorObj, SIZEOBJ);
	sizeObj = GetVar(colorObj, SIZEOBJ);
	if (sizeObj)
	{
	    SetVar(balls, SIZEOBJ, sizeObj);
	    SetVar(balls, SIZESWITCH, NewInt(1));
	}
    }

    return ObjTrue;
}

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

void InitBalls()
/*Initializes balls visualization object*/
{
    ObjPtr icon;

    /*Class for any object which can be sized*/
    visSized = NewObject(visGeometryClass, 0);
    AddToReferenceList(visSized);
    SetMethod(visSized, ADDCONTROLS, AddSizedControls);
    icon = NewIcon(0, 0, ICONVISSIZE, "Size");
    SetVar(icon, HELPSTRING,
	NewString("Select this icon to show controls to determine \
the size of the elements of the visualization object."));
    SetVar(visSized, CONTROLICON, icon);

    SetVar(visSized, SIZEFACTOR, NewReal(1.0));
    DeclareIndirectDependency(visSized, SURFACE, SIZEOBJ, CURDATA);
    DeclareIndirectDependency(visSized, SURFACE, SIZEOBJ, CURDATAFORM);
    DeclareDependency(visSized, SURFACE, SIZEOBJ);
    DeclareDependency(visSized, SURFACE, SIZECONSTANT);
    DeclareDependency(visSized, SURFACE, SIZEOFFSET);
    DeclareDependency(visSized, SURFACE, SIZEFACTOR);
    DeclareDependency(visSized, SURFACE, SIZESWITCH);
    /*DIKEO dependency on INTERPwhatever*/

    /*Class for a ball cloud*/
    visBalls = NewObject(visSized, 0);
    AddToReferenceList(visBalls);
    SetVar(visBalls, NAME, NewString("Balls"));
    SetVar(visBalls, POINTSTYLE, NewInt(BS_OCTOHEDRA));
    SetMethod(visBalls, INITIALIZE, BallsInit);
    SetVar(visBalls, SIZEOFFSET, NewReal(0.0));
    SetVar(visBalls, SIZEFACTOR, NewReal(1.0));
    SetVar(visBalls, SIZECONSTANT, NewReal(1.0));
    SetVar(visBalls, DEFAULTICON, icon = NewObject(visIcon, 0));
    SetVar(icon, WHICHICON, NewInt(ICONBALLS));
    SetVar(icon, NAME, NewString("Balls"));
    SetVar(icon, HELPSTRING,
	NewString("This icon represents a ball cloud or ball display object.  \
This object can show a ball cloud or group of balls for just about any data field."));
    DeclareIndirectDependency(visBalls, SURFACE, MAINDATASET, DATA);
    DeclareIndirectDependency(visBalls, SURFACE, MAINDATASET, CURDATA);
    DeclareIndirectDependency(visBalls, SURFACE, MAINDATASET, CURDATAFORM);
    DeclareIndirectDependency(visBalls, SURFACE, MAINDATASET);
    DeclareDependency(visBalls, SURFACE, POINTSTYLE);
    SetMethod(visBalls, SURFACE, MakeBallsSurface);

    SetMethod(visBalls, SETMAINDATASET, SetBallsMainDataset);
    SetVar(visBalls, STYLE, NewInt(0));

    SetMethod(visBalls, ADDCONTROLS, AddBallsControls);
    icon = NewIcon(0, 0, ICONBALLS, "Balls");
    SetVar(icon, HELPSTRING,
	NewString("Click on this icon to see a panel of controls for the ball object."));
    SetVar(visBalls, CONTROLICON, icon);

    DefineVisMapping(DS_HASFORM | DS_HASFIELD | DS_VECTOR, -1, -1, -1, visBalls);
    DefineVisMapping(DS_HASFORM | DS_HASFIELD, -1, -1, -1, visBalls);
    DefineVisMapping(DS_HASFORM | DS_HASFIELD | DS_VECTOR | DS_UNSTRUCTURED, -1, -1, -1, visBalls);
    DefineVisMapping(DS_HASFORM | DS_HASFIELD | DS_UNSTRUCTURED, -1, -1, -1, visBalls);
}

void KillBalls()
/*Kills balls visualization*/
{
    DeleteThing(visBalls);
}
