/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>



#include <string.h>
#include "fieldClass.h"
#include "internals.h"

static
Array
add(Field f, int start, int n, Pointer c, Object ct, int per)
{
    Array a = (Array) DXGetComponentValue(f, CONNECTIONS);
    if (!a) {
	a = DXNewArray(TYPE_INT, CATEGORY_REAL, 1, per);
	if (!a)
	    return NULL;
	DXSetComponentValue(f, CONNECTIONS, (Object) a);
	DXSetAttribute((Object)a, ELEMENT_TYPE, ct);
    } else {
	Object t;
	t = DXGetComponentAttribute(f, CONNECTIONS, ELEMENT_TYPE);
	if (!t)
	    DXErrorReturn(ERROR_BAD_PARAMETER,
		"Connections component is missing element type attribute");
	if (t!=ct) {
	    char *s, *cs;
	    s = DXGetString((String)t);
	    cs = DXGetString((String)ct);
	    if (!s || !cs)
		return NULL;
	    if (strcmp(cs,s) != 0)
		DXErrorReturn(ERROR_BAD_PARAMETER, "Connections type conflict");
	}
    }
    if (!DXAddArrayData(a, start, n, c))
	return NULL;
    return a;
}

/*
 * begin/end cell
 */

Field _dxfBeginCell(Field f)
{
    CHECK(f, CLASS_FIELD);
    f->cell = 1;
    return f;
}

Field _dxfEndCell(Field f)
{
    CHECK(f, CLASS_FIELD);
    f->cell = 0;
    return f;
}


/*
 * beginface, next point, end face
 */

Field
_dxfBeginFace(Field f)
{
    CHECK(f, CLASS_FIELD);
    f->npts = 0;
    return f;
}

Field
_dxfNextPoint(Field f, PointId id)
{
    CHECK(f, CLASS_FIELD);
    if (f->npts==f->pts_alloc) {
	int n = f->pts_alloc? f->pts_alloc*3/2 : 100;
	PointId *p = (PointId *) DXReAllocate((Pointer)f->pts, n*sizeof(int));
	if (!p)
	    return NULL;
	f->pts = p;
	f->pts_alloc = n;
    }
    f->pts[f->npts++] = id;
}


/*
 * For volume faces, canonically decompose the face
 * into triangles and put into hash table, distinguishing
 * between surface and inner faces.
 *
 * XXX - what if hash table fills up?
 *       doesn't preserve sense of surface faces
 */

static
Field
_EndFaceVolume(Field f)
{
    int npoints, min, i, dir, h;
    Triangle t;

    if (f->npts<3)
	return NULL;

    /* create hash table */
    if (!f->hash) {
	DXMarkTime("start faces");
	if (!DXGetArrayInfo((Array)DXGetComponentValue(f, POSITIONS), &npoints,
	  NULL, NULL, NULL, NULL))
	    DXErrorReturn(ERROR_MISSING_DATA,
			"points component missing in EndFace");
	for (f->hash_alloc=1; f->hash_alloc<10*npoints; f->hash_alloc*=2)
	    continue;
	f->hash = (struct hash *)
	    DXAllocate(f->hash_alloc*sizeof(struct hash));
	if (!f->hash)
	    return NULL;
	memset(f->hash, 0, f->hash_alloc*sizeof(struct hash));
	/* free old surface and inner faces */
	DXSetComponentValue(f, SURFACE, NULL);
	DXSetComponentValue(f, INNER, NULL);
    }

    /* canonical starting point is min vertex number */
    for (min=0, i=1; i<f->npts; i++)
	if (f->pts[i] < f->pts[min])
	    min = i;

#define PT(i) (f->pts[(i)%(f->npts)])

    /* canonical direction: pick smallest neighbor of min */
    dir = PT(min+1)<PT(min-1)? 0 : 1;

    /* f->npts-2 triangles */
    for (i=1; i<f->npts-1; i++) {

	/* here's the triangle */
	t.p = PT(min);
	t.q = PT(min+i+(dir));
	t.r = PT(min+i+(1-dir));

	/* put in hash table */
	h = (t.p + 17*t.q + 513*t.r) & (f->hash_alloc-1);
	while (f->hash[h].count && (f->hash[h].tri.p!=t.p
	       || f->hash[h].tri.q!=t.q || f->hash[h].tri.r!=t.r))
	    h = (h+1) & (f->hash_alloc-1);
	f->hash[h].tri = t;
	f->hash[h].count += 1;
	if (f->hash[h].count==1)	/* new face: */
	    f->nsurface += 1;		/*     assume surface */
	else if (f->hash[h].count&1) {	/* count now odd, was even: */
	    f->nsurface += 1;		/*     now surface */
	    f->ninner -= 1;		/*     was inner */
	} else {			/* count now even, was odd: */
	    f->ninner += 1;		/*     now inner */
	    f->nsurface -= 1;		/*     was surface */
	}
    }

    f->npts = 0;

    return f;
}


/*
 * For surface faces, just put the faces into "triangle" component.
 */

static
Field
_EndFaceSurface(Field f)
{
    int n, i;
    Triangle t;
    Array a;

    /* allocate "triangle" component */
    a = add(f, 0, 0, NULL, O_TRIANGLES, 3);
    if (!a)
	return NULL;

    /* starting face number */
    if (!DXGetArrayInfo(a, &n, NULL, NULL, NULL, NULL))
	DXErrorReturn(ERROR_INTERNAL, "DXGetArrayInfo failed in EndFace!");

    /* add f->npts-2 triangles starting at face n */
    for (i=1; i<f->npts-1; i++, n++) {
	t.p = f->pts[0];
	t.q = f->pts[i];
	t.r = f->pts[i+1];
	if (!DXAddArrayData(a, n, 1, (Pointer)&t))
	    return NULL;
    }

    f->npts = 0;

    return f;
}

Field
_dxfEndFace(Field f)
{
    CHECK(f, CLASS_FIELD);

    if (f->cell)
	return _EndFaceVolume(f);
    else
	return _EndFaceSurface(f);
}


/*
 * end field
 */

static int trace = 0;

void
_dxfTraceField(int t)
{
    trace = t;
}

/*
 * These variables allow for sharing of strings
 */

Field
DXEndField(Field f)
{
    Triangle *surface, *inner;
    int npoints=0, i; 
    int ns, ni, np, nc, n;
    Array a, ap, ac;
    Object o;
    char *name;
    static struct {
	char **component;			/* the component */
	char **attribute;			/* its attribute */
	Object *value;				/* the attribute's value */
	char **name;				/* dependent's name */
	int n;					/* number of items in value */
    } *s, standard[] = {			/* standard comp attributes */
	&POSITIONS,	&DEP,	&O_POSITIONS,   &POSITIONS,   -1,
	&CONNECTIONS,	&DEP,	&O_CONNECTIONS, &CONNECTIONS, -1,
	&CONNECTIONS,	&REF,	&O_POSITIONS,   &POSITIONS,   -1,
	&DATA,		&DEP,	&O_POSITIONS,   &POSITIONS,   -1,
	&COLORS,	&DEP,	&O_POSITIONS,   &POSITIONS,   -1,
	&IMAGE,		&DEP,	&O_POSITIONS,   &POSITIONS,   -1,
	&FRONT_COLORS,  &DEP,	&O_POSITIONS,   &POSITIONS,   -1,
	&BACK_COLORS,	&DEP,	&O_POSITIONS,   &POSITIONS,   -1,
	&OPACITIES,	&DEP,	&O_POSITIONS,   &POSITIONS,   -1,
	&TANGENTS,	&DEP,	&O_POSITIONS,   &POSITIONS,   -1,
	&NORMALS,	&DEP,	&O_POSITIONS,   &POSITIONS,   -1,
	&BINORMALS,	&DEP,	&O_POSITIONS,   &POSITIONS,   -1,
	NULL    	    
    };

    CHECK(f, CLASS_FIELD);


    /* trim component arrays */
    for (i=0; o=DXGetEnumeratedComponentValue(f, i, NULL); i++)
	if (DXGetObjectClass(o)==CLASS_ARRAY)
	    if (!DXTrim((Array)o))
		return NULL;

    /* free ->pts array */
    if (f->pts) {
	DXFree((Pointer)f->pts);
	f->pts_alloc = 0;
    }

    /* copy hash table of faces */
    if (f->hash) {

	DXMarkTime("end faces");
	DXMarkTime("start copy");

	/* surface */
	a = DXNewArray(TYPE_INT, CATEGORY_REAL, 1, 3);
	if (!a)
	    return NULL;
	DXSetComponentValue(f, SURFACE, (Object)a);
	DXSetAttribute((Object)a, REF, O_POSITIONS);
	DXAddArrayData(a, 0, f->nsurface, NULL);
	surface = (Triangle *) DXGetArrayData(a);

	/* inner */
	a = DXNewArray(TYPE_INT, CATEGORY_REAL, 1, 3);
	if (!a)
	    return NULL;
	DXSetComponentValue(f, INNER, (Object)a);
	DXSetAttribute((Object)a, REF, O_POSITIONS);
	DXAddArrayData(a, 0, f->ninner, NULL);
	inner = (Triangle *) DXGetArrayData(a);

	/* copy surface and inner faces in */
	for (i=0, ns=0, ni=0; i<f->hash_alloc; i++) {
	    if (f->hash[i].count&1)		/* odd count => surface face */
		surface[ns++] = f->hash[i].tri;
	    else if (f->hash[i].count!=0)	/* even count => inner face */
		inner[ni++] = f->hash[i].tri;
	}
	ASSERT(ns==f->nsurface && ni==f->ninner);

	DXFree((Pointer)f->hash);
	f->hash = NULL;
	f->hash_alloc = 0;

	DXMarkTime("end copy");
    }

    /* pre-compute bounding box */
    if (!DXBoundingBox((Object) f, NULL) && (DXGetError()!=ERROR_NONE))
	return NULL;

    /*
     * Pre-compute volume element neighbors.
     * These are done ahead of time to prevent the race condition
     * that arises if done on-the-fly in rendering.  In other
     * modules, which parallelize by data, it is ok to do them on
     * the fly.
     */
    if (!DXGetComponentValue(f, NEIGHBORS)) {
	if (!_dxf_TetraNeighbors(f) && DXGetError()!=ERROR_NONE)
	    return NULL;
	if (!_dxf_CubeNeighbors(f) && DXGetError()!=ERROR_NONE)
	    return NULL;
    }

    /* if we decide at some point to precompute statistics, here is one
     * place where it makes sense to put the call.  the other is in the
     * DXEndObject() routine, since stats can go parallel and needs to
     * compute stats on compositefields differently than normal fields.
     *
     * we currently do not precompute statistics because it can be expensive
     * in time for large arrays, and if there are invalid points the
     * stats have to be computed twice, once with and without considering
     * the invalids.  if the stats aren't needed, this is unnecessary work.
     * the argument for precomputing them is to avoid the stats call modifying
     * the input to a module.  the statistics() call adds a statistics array to
     * the input field so subsequent calls for stats do not have to recompute
     * the same values.  normally modifying the input to a module is a very bad
     * thing because if two tasks in one module modify the same input, there is
     * no locking at the field level and the result can be garbage.  in fact i
     * think this is the only routine we knowingly allow to modify inputs.
     * we allow it to avoid precomputing stats on every field every time, and
     * don't have problems because first we don't allow multiple modules
     * to execute at the same time so two different modules can't be computing
     * statistics on the same field at the same time, and second most modules
     * go parallel by giving different fields to each task, so even if the
     * tasks are executing at the same time, they are generally working on
     * different fields.  also, many modules need the min/max before going
     * to the task level, so the stats are computed before going parallel.
     * it is a potential source of problems however.
     */

    /* i moved the following code down to here.  it used to be the first code
     * in the module, but it makes more sense to be here next to the code
     * which uses these values.   nsc 06jun96 
     */

    /* number of items in value */
    np = nc = -1;
    ap = (Array)DXGetComponentValue(f, POSITIONS);
    if (ap && !DXGetArrayInfo(ap, &np, NULL, NULL, NULL, NULL))
	return NULL;
    ac = (Array)DXGetComponentValue(f, CONNECTIONS);
    if (ac && !DXGetArrayInfo(ac, &nc, NULL, NULL, NULL, NULL))
	return NULL;
    for (s=standard; s->component; s++) {
	if (*s->name == POSITIONS)
	    s->n = np;
	else if (*s->name == CONNECTIONS)
	    s->n = nc;
	else
	    s->n = -1;
    }

    /*
     * set standard component attributes
     * XXX - this depends on knowing that fields always store the
     * name as a pointer to one of the standard strings
     */
    for (i=0; o=DXGetEnumeratedComponentValue(f, i, &name); i++) {
	for (s=standard; s->component; s++) {
	    if (name==*s->component && s->n>=0) {
		if (!DXGetAttribute(o, *s->attribute)) {
		    if (*s->attribute==DEP) {
			if (!DXGetArrayInfo((Array)o,&n,NULL,NULL,NULL,NULL))
			    return NULL;
			if (n!=s->n) {
			    DXSetError(ERROR_INVALID_DATA,
				     "The \"%s\" component has %d elements.  "
				     "By default, it depends on the \"%s\" "
				     "component, which has %d elements.  "
				     "A valid \"dep\" attribute should "
				     "be specified for the \"%s\" component "
				     "to override the default.",
				     *s->component, n, *s->name, s->n,
				     *s->component);
			    return NULL;
			}
		    }
		    DXSetAttribute(o, *s->attribute, *s->value);
		}
		/* break; */
	    }
	}
    }

    if (trace) {
	int npoints=0, nsurface=0, ninner=0;
	DXGetArrayInfo((Array)DXGetComponentValue(f, POSITIONS), &npoints,
		     NULL, NULL, NULL, NULL);
	DXGetArrayInfo((Array)DXGetComponentValue(f, SURFACE), &nsurface,
		     NULL, NULL, NULL, NULL);
	DXGetArrayInfo((Array)DXGetComponentValue(f, INNER), &ninner,
		     NULL, NULL, NULL, NULL);
	DXDebug("F", "field 0x%x: %d points, %d surface, %d inner",
		f, npoints, nsurface, ninner);
    }

    return f;
}


/*
 * ChangedXXX routines
 */

/* true if there is an attr named `a', the contents are a single string,
 *  and the contents match the target component name.
 */
#define ATTR(a) (DXGetStringAttribute(c, a, &cp) && !strcmp(cp, component))

/* true if there is an attr named `a', the contents are either a single
 *  string or a string list, and if any member of the stringlist matches
 *  the target component name.
 */
static int attrlist(Object c, char *a, char *component)
{
    Object o;
    int l;
    char *cp;
    
    if (!(o = DXGetAttribute(c, a)))
	return 0;
    
    for (l=0; DXExtractNthString(o, l, &cp); l++)
	if (!strcmp(cp, component))
	    return 1;

    return 0;
}

Field
DXChangedComponentStructure(Field f, char *component)
{
    int i, n;
    Object c, o;
    char *names[100], *cp;

    for (i=0, n=0; c=DXGetEnumeratedComponentValue(f, i, &names[n]); i++)
	if (ATTR(DEP) || ATTR(REF) || attrlist(c, DER, component))
	    if (strcmp(names[n],component) != 0)
		n++;
    ASSERT(n<100);
    for (i=0; i<n; i++) {
	DXDeleteComponent(f, names[i]);
	DXChangedComponentStructure(f, names[i]);
    }
    return f;
}    

Field
DXChangedComponentValues(Field f, char *component)
{
    int i, l, n;
    Object o, c;
    char *names[100];

    for (i=0, n=0; c=DXGetEnumeratedComponentValue(f, i, &names[n]); i++)
	if (attrlist(c, DER, component))
	    if (strcmp(names[n],component) != 0)
		n++;
    ASSERT(n<100);
    for (i=0; i<n; i++) {
	DXDeleteComponent(f, names[i]);
	DXChangedComponentStructure(f, names[i]);
    }
    return f;
}    


/*
 * DXAdd routines
 */

Field
DXAddPoints(Field f, int start, int n, Point *p)
{
    Array a = (Array) DXGetComponentValue(f, POSITIONS);
    if (!a) {
	a = DXNewArray(TYPE_FLOAT, CATEGORY_REAL, 1, 3);
	if (!a)
	    return NULL;
	DXSetComponentValue(f, POSITIONS, (Object)a);
	DXSetAttribute((Object)a, DEP, O_POSITIONS);
    }
    if (!DXAddArrayData(a, start, n, (Pointer)p))
	return NULL;
    return f;
}

Field
DXAddPoint(Field f, int id, Point p)
{
    return DXAddPoints(f, id, 1, &p);
}

Field
DXAddColors(Field f, int start, int n, RGBColor *c)
{
    Array a = (Array) DXGetComponentValue(f, COLORS);
    if (!a) {
	a = DXNewArray(TYPE_FLOAT, CATEGORY_REAL, 1, 3);
	if (!a)
	    return NULL;
	DXSetComponentValue(f, COLORS, (Object)a);
	DXSetAttribute((Object)a, DEP, O_POSITIONS);
    }
    if (!DXAddArrayData(a, start, n, (Pointer)c))
	return NULL;
    return f;
}

Field
DXAddColor(Field f, PointId id, RGBColor c)
{
    return DXAddColors(f, id, 1, &c);
}

Field
DXAddFrontColors(Field f, int start, int n, RGBColor *c)
{
    Array a = (Array) DXGetComponentValue(f, FRONT_COLORS);
    if (!a) {
	a = DXNewArray(TYPE_FLOAT, CATEGORY_REAL, 1, 3);
	if (!a)
	    return NULL;
	DXSetComponentValue(f, FRONT_COLORS, (Object)a);
	DXSetAttribute((Object)a, DEP, O_POSITIONS);
    }
    if (!DXAddArrayData(a, start, n, (Pointer)c))
	return NULL;
    return f;
}

Field
DXAddFrontColor(Field f, PointId id, RGBColor c)
{
    return DXAddFrontColors(f, id, 1, &c);
}

Field
DXAddBackColors(Field f, int start, int n, RGBColor *c)
{
    Array a = (Array) DXGetComponentValue(f, BACK_COLORS);
    if (!a) {
	a = DXNewArray(TYPE_FLOAT, CATEGORY_REAL, 1, 3);
	if (!a)
	    return NULL;
	DXSetComponentValue(f, BACK_COLORS, (Object)a);
	DXSetAttribute((Object)a, DEP, O_POSITIONS);
    }
    if (!DXAddArrayData(a, start, n, (Pointer)c))
	return NULL;
    return f;
}

Field
DXAddBackColor(Field f, PointId id, RGBColor c)
{
    return DXAddBackColors(f, id, 1, &c);
}

Field
DXAddOpacities(Field f, int start, int n, float *o)
{
    Array a = (Array) DXGetComponentValue(f, OPACITIES);
    if (!a) {
	a = DXNewArray(TYPE_FLOAT, CATEGORY_REAL, 0);
	if (!a)
	    return NULL;
	DXSetComponentValue(f, OPACITIES, (Object)a);
	DXSetAttribute((Object)a, DEP, O_POSITIONS);
    }
    if (!DXAddArrayData(a, start, n, (Pointer)o))
	return NULL;
    return f;
}

Field
DXAddOpacity(Field f, int id, double o)
{
    float fo = o;
    return DXAddOpacities(f, id, 1, &fo);
}

Field
DXAddNormals(Field f, int start, int n, Vector *v)
{
    Array a = (Array) DXGetComponentValue(f, NORMALS);
    if (!a) {
	a = DXNewArray(TYPE_FLOAT, CATEGORY_REAL, 1, 3);
	if (!a)
	    return NULL;
	DXSetComponentValue(f, NORMALS, (Object) a);
	DXSetAttribute((Object)a, DEP, O_POSITIONS);
    }
    /* XXX - check dep of exsiting component */
    if (!DXAddArrayData(a, start, n, (Pointer) v))
	return NULL;
    return f;
}

Field
DXAddNormal(Field f, PointId id, Vector v)
{
    return DXAddNormals(f, id, 1, &v);
}

Field
DXAddFaceNormals(Field f, int start, int n, Vector *v)
{
    Array a = (Array) DXGetComponentValue(f, NORMALS);
    if (!a) {
	a = DXNewArray(TYPE_FLOAT, CATEGORY_REAL, 1, 3);
	if (!a)
	    return NULL;
	DXSetComponentValue(f, NORMALS, (Object) a);
	DXSetAttribute((Object)a, DEP, O_CONNECTIONS);
    }
    /* XXX - check dep of exsiting component */
    if (!DXAddArrayData(a, start, n, (Pointer) v))
	return NULL;
    return f;
}

Field
DXAddFaceNormal(Field f, PointId id, Vector v)
{
    return DXAddFaceNormals(f, id, 1, &v);
}

Field
DXAddLines(Field f, int start, int n, Line *l)
{
    return add(f, start, n, (Pointer)l, O_LINES, 2)? f : NULL;
}

Field
DXAddLine(Field f, PointId id, Line l)
{
    return DXAddLines(f, id, 1, &l);
}

Field
DXAddTriangles(Field f, int start, int n, Triangle *t)
{
    return add(f, start, n, (Pointer)t, O_TRIANGLES, 3)? f : NULL;
}

Field
DXAddTriangle(Field f, PointId id, Triangle t)
{
    return DXAddTriangles(f, id, 1, &t);
}

Field
DXAddQuads(Field f, int start, int n, Quadrilateral *q)
{
    return add(f, start, n, (Pointer)q, O_QUADS, 4)? f : NULL;
}

Field
DXAddQuad(Field f, PointId id, Quadrilateral q)
{
    return DXAddQuads(f, id, 1, &q);
}

Field
DXAddTetrahedra(Field f, int start, int n, Tetrahedron *t)
{
    return add(f, start, n, (Pointer)t, O_TETRAHEDRA, 4)? f : NULL;
}

Field
DXAddTetrahedron(Field f, PointId id, Tetrahedron t)
{
    return DXAddTetrahedra(f, id, 1, &t);
}

Array
DXGetConnections(Field f, char *type)
{
    Array a;
    char *ct;
    a = (Array) DXGetComponentValue(f, CONNECTIONS);
    if (!a)
	return NULL;
    ct = DXGetString((String)DXGetAttribute((Object)a, ELEMENT_TYPE));
    if (!ct || strcmp(ct, type)!=0)
	return NULL;
    return a;
}

Field
DXSetConnections(Field f, char *type, Array a)
{
    if (!DXSetComponentValue(f, CONNECTIONS, (Object)a))
	return NULL;
    if (!DXSetAttribute((Object)a, ELEMENT_TYPE, _dxfstringobject(type, 0)))
	return NULL;
    return f;
}



/*
 * Object-level interface for sharing neighbors and box components
 * among objects that share positions and connections
 */

#define POSITIONS_ENTRY		0x01
#define CONNECTIONS_ENTRY	0x02

typedef struct 
{
    Object a; 	 /* array			    */
    int    t;    /* positions or connections entry  */
    Field  f; 	 /* prototypical field		    */
    Array  r; 	 /* result array (box or neighbors) */
} HElement;

#define PASS1	1
#define PASS2	2

/*
 * Traverse an object to the field level.  For each field we do one of two
 * things depending on the pass.  For pass1 we ensure that there's a hash 
 * element for each unique positions or connections component.  For pass2
 * we access the hash table based on the positions connections to get a 
 * bounding box component, and based on the connections component to get a
 * neighbors component (if one was created: recall that we don't create 
 * neighbors for regular connections.  In pass2 we also call DXEndField.
 */
static Error
_Recur(Object o, HashTable hTable, int pass)
{
    if (! o)
	return OK;

    switch(DXGetObjectClass(o))
    {
	case CLASS_GROUP:
	{
	    int i;
	    Object c;

	    i = 0;
	    while ((c = DXGetEnumeratedMember((Group)o, i++, NULL)) != NULL)
		if (! _Recur(c, hTable, pass))
		    goto error;
	    
	    break;
	}

	case CLASS_XFORM:
	{
	    Object c;

	    if (! DXGetXformInfo((Xform)o, &c, NULL))
		goto error;

	    if (! _Recur(c, hTable, pass))
		goto error;
		
	    break;
	}
	
	case CLASS_SCREEN:
	{
	    Object c;

	    if (! DXGetScreenInfo((Screen)o, &c, NULL, NULL))
		goto error;

	    if (! _Recur(c, hTable, pass))
		goto error;
	    
	    break;
	}
	
	case CLASS_CLIPPED:
	{
	    Object c;

	    if (! DXGetClippedInfo((Clipped)o, &c, NULL))
		goto error;

	    if (! _Recur(c, hTable, pass))
		goto error;

	    break;
	}

	case CLASS_FIELD:
	{
	    HElement helt, *hptr;
	    Field f = (Field)o;

	    if (DXEmptyField(f))
		break;
	    
	    helt.f = f;
	    helt.r = NULL;

	    if (pass == PASS1)
	    {
		helt.a = DXGetComponentValue(f, "positions");
		if (helt.a)
		{
		    helt.t = POSITIONS_ENTRY;

		    if (! DXQueryHashElement(hTable, (Key)&helt))
			if (! DXInsertHashElement(hTable, (Element)&helt))
			    goto error;
		}

		helt.a = DXGetComponentValue(f, "connections");
		if (helt.a)
		{
		    helt.t = CONNECTIONS_ENTRY;

		    if (! DXQueryHashElement(hTable, (Key)&helt))
			if (! DXInsertHashElement(hTable, (Element)&helt))
			    goto error;
		}
	    }
	    else
	    {
		Object str;

		helt.a = DXGetComponentValue(f, "positions");
		if (helt.a)
		{
		    hptr = (HElement *)DXQueryHashElement(hTable, (Key)&helt);
		    if (! hptr)
		    {
			DXSetError(ERROR_INTERNAL, 
			    "DXEndObject: positions entry missing in pass2");
			goto error;
		    }

		    if (hptr->t != POSITIONS_ENTRY)
		    {
			DXSetError(ERROR_INTERNAL, "DXEndObject: entry type error");
			goto error;
		    }

		    if (hptr->r)
		    {
			if (! DXSetComponentValue(f, "box", (Object)hptr->r))
			    goto error;
		    
			str = (Object)DXNewString("positions");
			if (! str)
			    goto error;

			if (! DXSetComponentAttribute(f, "box", "der", str))
			{
			    DXDelete(str);
			    goto error;
			}
		    }
		}

		helt.a = DXGetComponentValue(f, "connections");
		if (helt.a)
		{
		    hptr = (HElement *)DXQueryHashElement(hTable, (Key)&helt);
		    if (! hptr)
		    {
			DXSetError(ERROR_INTERNAL, 
			    "DXEndObject: connections entry missing in pass2");
			goto error;
		    }

		    if (hptr->t != CONNECTIONS_ENTRY)
		    {
			DXSetError(ERROR_INTERNAL, "DXEndObject: entry type error");
			goto error;
		    }

		    if (hptr->r)
		    {
			if (!DXSetComponentValue(f, "neighbors", (Object)hptr->r))
			    goto error;
		
			str = (Object)DXNewString("connections");
			if (! str)
			    goto error;

			if (! DXSetComponentAttribute(f, "neighbors", "dep", str))
			{
			    DXDelete(str);
			    goto error;
			}
					    
			if (! DXSetComponentAttribute(f, "neighbors", "ref", str))
			{
			    DXDelete(str);
			    goto error;
			}
		    }
		}

#ifndef DO_ENDFIELD
		if (! DXEndField(f))
		    goto error;
#endif

	    }

	    break;
	}

	default:
	    break;
    }

    return OK;

error:
    return ERROR;
}

/* 
 * Task executed for each hash table entry.  If the entry is of type
 * POSITIONS_ENTRY, then compute the bounding box and stick it into the
 * result slot of the hash element.  Otherwise, it ought to be of type
 * CONNECTIONS_ENTRY, so we compute a neighbors component and put it into
 * the hash element.
 */
static Error
EndObjectTask(Pointer ptr)
{
    HElement *hPtr = *(HElement **)ptr;

    if (hPtr->t == POSITIONS_ENTRY)
    {
	if (DXBoundingBox((Object)(hPtr->f), NULL))
	{
	    hPtr->r = (Array)DXGetComponentValue(hPtr->f, "box");
	    if (! hPtr->r)
	    {
		if (DXGetError() == ERROR_NONE)
		    DXSetError(ERROR_INTERNAL, 
		      "DXBoundingBox succeeded but no box component was found");
		goto error;
	    }
	}
	else
	{
	    if (DXGetError() != ERROR_NONE)
		goto error;
	    
	    hPtr->r = NULL;
	}
    }
    else if (hPtr->t == CONNECTIONS_ENTRY)
    {
	hPtr->r = DXNeighbors(hPtr->f);

	if (DXGetError() != ERROR_NONE)
	    goto error;
    }
    else
    {
	DXSetError(ERROR_INTERNAL, "unknown entry type");
	goto error;
    }

    return OK;

error:
    return ERROR;
}

Object
DXEndObject(Object o)
{
    HashTable hTable = NULL;
    HElement *elt;

    hTable = DXCreateHash(sizeof(HElement), NULL, NULL);
    if (! hTable)
	goto error;
    
    /*
     * Traverse the input, creating a hash entry of type POSITIONS_ENTRY
     * for each unique positions array and a entry of type CONNECTIONS_ENTRY
     * for each unique connections array.
     */
    if (! _Recur(o, hTable, PASS1))
	goto error;
    
    /*
     * Traverse the hash table.  For each element of type CONNECTIONS_ENTRY,
     * create a task that will create a neighbors array using the template
     * field.
     */
    if (! DXCreateTaskGroup())
	goto error;

    DXInitGetNextHashElement(hTable);
    while ((elt = (HElement *)DXGetNextHashElement(hTable)) != NULL)
    {
	if (elt->t == CONNECTIONS_ENTRY)
	    if (! DXAddTask(EndObjectTask, (Pointer)&elt, sizeof(Pointer), 1))
	    {
		DXAbortTaskGroup();
		goto error;
	    }
    }

    if (! DXExecuteTaskGroup() || DXGetError() != ERROR_NONE)
	goto error;
    
    /*
     * Traverse the hash table again.  For each element of type
     * CONNECTIONS_ENTRY, * create a task that will create a box array
     * using the template field.
     */
    if (! DXCreateTaskGroup())
	goto error;

    DXInitGetNextHashElement(hTable);
    while ((elt = (HElement *)DXGetNextHashElement(hTable)) != NULL)
    {
	if (elt->t == POSITIONS_ENTRY)
	    if (! DXAddTask(EndObjectTask, (Pointer)&elt, sizeof(Pointer), 1))
	    {
		DXAbortTaskGroup();
		goto error;
	    }
    }

    if (! DXExecuteTaskGroup() || DXGetError() != ERROR_NONE)
	goto error;
    
    /*
     * Traverse the input again.  For each field, access the hash table keyed 
     * by the positions component address to get a box component and by the
     * connections component address to get a neighbors component.  Then call
     * DXEndField on each.
     */
    if (! _Recur(o, hTable, PASS2))
	goto error;
    
    DXDestroyHash(hTable);
    
    return o;

error:
    DXDestroyHash(hTable);
    return NULL;
}



