[BACK]Return to pfdGeoBuilder.c CVS log [TXT][DIR] Up to [Development] / performer / src / lib / libpfdu

File: [Development] / performer / src / lib / libpfdu / pfdGeoBuilder.c (download)

Revision 1.1, Tue Nov 21 21:39:35 2000 UTC (16 years, 10 months ago) by flynnt
Branch: MAIN
CVS Tags: HEAD

Initial check-in based on OpenGL Performer 2.4 tree.
-flynnt

/*
 * pfdGeoBuilder.c
 *
 * $Revision: 1.1 $
 * $Date: 2000/11/21 21:39:35 $
 *
 * The geobuilder is a low-level state-free geometry construction
 * library. It is used by the pfdBuilder to build IRIS Performer
 * scene graphs.
 *
 *
 * Copyright 1995, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 *
 * UNPUBLISHED -- Rights reserved under the copyright laws of the United
 * States.   Use of a copyright notice is precautionary only and does not
 * imply publication or disclosure.
 *
 * U.S. GOVERNMENT RESTRICTED RIGHTS LEGEND:
 * Use, duplication or disclosure by the Government is subject to restrictions
 * as set forth in FAR 52.227.19(c)(2) or subparagraph (c)(1)(ii) of the Rights
 * in Technical Data and Computer Software clause at DFARS 252.227-7013 and/or
 * in similar or successor clauses in the FAR, or the DOD or NASA FAR
 * Supplement.  Contractor/manufacturer is Silicon Graphics, Inc.,
 * 2011 N. Shoreline Blvd. Mountain View, CA 94039-7311.
 *
 * THE CONTENT OF THIS WORK CONTAINS CONFIDENTIAL AND PROPRIETARY
 * INFORMATION OF SILICON GRAPHICS, INC. ANY DUPLICATION, MODIFICATION,
 * DISTRIBUTION, OR DISCLOSURE IN ANY FORM, IN WHOLE, OR IN PART, IS STRICTLY
 * PROHIBITED WITHOUT THE PRIOR EXPRESS WRITTEN PERMISSION OF SILICON
 * GRAPHICS, INC.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#ifndef __linux__
#include <bstring.h>
#else
#include <string.h>
#endif
#include <alloca.h>

#include <Performer/pf.h>
#include <Performer/pfdu.h>
#include <Performer/pfutil.h>

/* ###################################################################### */
/* #                                                                    # */
/* #  Private pfdGeoBuilder data structures and function declarations   # */
/* #                                                                    # */
/* ###################################################################### */

#define SORTOF 1.0e-4f

/* #define CHECK_ALLOCATIONS /* */
#define CHECK_DUPLICATE

struct _pfdGeoBuilder
{
    pfdPrim	    *triList;
    pfdPrim	    *availTri;

    pfVec3	    *coordList;
    pfVec3	    *normList;
    pfVec2	    *texCoordList[PF_MAX_TEXTURES];
    pfVec4	    *colorList;

    int             numPoints;
    int             pointCounts[4][4][PF_MAX_TEXTURES + 1];
    pfdPrim         *pointBins[4][4][PF_MAX_TEXTURES + 1];

    int             numIndexedPoints;
    int             indexedPointCounts[4][4][PF_MAX_TEXTURES + 1];
    pfdPrim         *indexedPointBins[4][4][PF_MAX_TEXTURES + 1];

    int	    	    numTris;
    int	    	    triCounts[4][4][PF_MAX_TEXTURES + 1];
    pfdPrim	    *triBins[4][4][PF_MAX_TEXTURES + 1];

    int	    	    numIndexedTris;
    int	    	    indexedTriCounts[4][4][PF_MAX_TEXTURES + 1];
    pfdPrim	    *indexedTriBins[4][4][PF_MAX_TEXTURES + 1];

    int	    	    numLines;
    int	    	    lineCounts[4][4][PF_MAX_TEXTURES + 1];
    pfdPrim	    *lineBins[4][4][PF_MAX_TEXTURES + 1];

    int	    	    numIndexedLines;
    int	    	    indexedLineCounts[4][4][PF_MAX_TEXTURES + 1];
    pfdPrim	    *indexedLineBins[4][4][PF_MAX_TEXTURES + 1];

    int	    	    numLineStrips;
    int	    	    linestripCounts[4][4][PF_MAX_TEXTURES + 1];
    pfdGeom	    *linestripBins[4][4][PF_MAX_TEXTURES + 1];

    int	    	    numIndexedLineStrips;
    int	    	    indexedlinestripCounts[4][4][PF_MAX_TEXTURES + 1];
    pfdGeom	    *indexedlinestripBins[4][4][PF_MAX_TEXTURES + 1];

    int             numFlatLineStrips;
    int             flatlinestripCounts[4][4][PF_MAX_TEXTURES + 1];
    pfdGeom         *flatlinestripBins[4][4][PF_MAX_TEXTURES + 1];

    int             numIndexedFlatLineStrips;
    int             indexedflatlinestripCounts[4][4][PF_MAX_TEXTURES + 1];
    pfdGeom         *indexedflatlinestripBins[4][4][PF_MAX_TEXTURES + 1];

    pfList	    *gsetList;

    int	    	    autoColors;
    int	    	    autoNormals;
    int	    	    autoTexture;
    int	    	    autoOrient;
    int	    	    collapse;
    int 	    mesh;
    int		    limit;
    int		    shareIndexLists;

#ifdef CHECK_DUPLICATE
    /* for quick comparison of coordinates */
    pfuHashTable    *hashTable;	    
#endif

    pfList	    *mallocList;
    pfList	    *mallocSizeList;

    void	    *sharedArena;
};

#define TRICHUNK	 128		/* initial allocation size */
#define MAXTRICHUNK	8192		/* maximum allocation size */
#define LINK_TRI	  69
#define HASHSIZE	   7

static pfdPrim * getPrim(pfdGeoBuilder *bldr);
static void addPoint(pfdGeoBuilder *bldr, pfdPrim *point);
static void addIndexedPoint(pfdGeoBuilder *bldr, pfdPrim *point);
static void addPoints(pfdGeoBuilder *bldr, pfdGeom *points);
static void addLine(pfdGeoBuilder *bldr, pfdPrim *line);
static void addIndexedLine(pfdGeoBuilder *bldr, pfdPrim *line);
static void addLines(pfdGeoBuilder *bldr, pfdGeom *lines);
static void addLineStrip(pfdGeoBuilder *bldr, pfdGeom *strip);
static void addIndexedLineStrip(pfdGeoBuilder *bldr, pfdGeom *strip);
static void addLineStrips(pfdGeoBuilder *bldr, pfdGeom *strip, int num);
static void addTri(pfdGeoBuilder *bldr, pfdPrim *tri);
static void addIndexedTri(pfdGeoBuilder *bldr, pfdPrim *tri);
static void addPoly(pfdGeoBuilder *bldr, pfdGeom *polygon);
static void copyPrim(pfdPrim *src, pfdPrim *dst);
static void copyGeom(pfdPrim *tri, pfdGeom *pgon, int v0, int v1, int v2);
static void copyLineStrip(pfdGeom *dst, pfdGeom *src);
static void moveTriList(pfdGeoBuilder *ts, int nb1, int cb1, int tb1, int nb2, int cb2, int tb2);
static void moveIndexedTriList(pfdGeoBuilder *ts, int nb1, int cb1, int tb1, int nb2, int cb2, int
tb2);
static void collapseAttr(pfdGeoBuilder *bldr, pfdPrim *tri, int attr);
static void freeStripBins(pfdGeom *bin[4][4][PF_MAX_TEXTURES + 1], int count[4][4][PF_MAX_TEXTURES + 1]);
static void makeTriGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind);
static void makeIndexedTriGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind);
static void makeLineGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind);
static void makeFlatLineGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind);
static void makeIndexedLineGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind);
static void makeIndexedFlatLineGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind);
static void makePointGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind);
static void makeIndexedPointGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind);
static void makeGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind);
static int decompConcave(pfdGeom *pgon, pfdPrim *triList, int asum, int x, int y);
static int triangulatePoly(pfdGeom *pgon, pfdPrim *triList);

#ifdef	OLD_STYLE_FREE
static void freeTris(pfdGeoBuilder *bldr);
#endif

/* ###################################################################### */
/* #                                                                    # */
/* #                    pfdGeoBuilder Functions                         # */
/* #                                                                    # */
/* ###################################################################### */

pfdGeoBuilder*
pfdNewGeoBldr(void)
{
    pfdGeoBuilder	*bldr;
    void *sharedArena = pfGetSharedArena();

    /* allocate builder structure in unshared memory */
    if ((bldr = (pfdGeoBuilder*) pfCalloc(1, sizeof(pfdGeoBuilder), sharedArena)) == NULL)
	return NULL;

    /* initialize shared arena pointer for this pfdGeoBuilder */
    bldr->sharedArena = sharedArena;

    /* start resulting geoset list off small ... it grows as needed */
    if ((bldr->gsetList = pfNewList(sizeof(pfGeoSet*), 1, NULL)) == NULL)
	return NULL;

    /* keep track of allocations for eventual deallocation */
    if ((bldr->mallocList = pfNewList(sizeof(void*),16,NULL)) == NULL)
	return NULL;

#ifdef CHECK_ALLOCATIONS
    /* keep track of allocation sizes as well */
    if ((bldr->mallocSizeList = pfNewList(sizeof(long),16,NULL)) == NULL)
	return NULL;
#endif

    /* allocate a chunk of primitives */
    if ((bldr->triList = pfCalloc(TRICHUNK, sizeof(pfdPrim), NULL)) == NULL)
	return NULL;
    pfAdd(bldr->mallocList, bldr->triList);
#ifdef CHECK_ALLOCATIONS
    pfAdd(bldr->mallocSizeList, (void *)(TRICHUNK*sizeof(pfdPrim)));
#endif

    bldr->triList[TRICHUNK-1].next  = bldr->triList;
    bldr->triList[TRICHUNK-1].flags = LINK_TRI;
    bldr->availTri = bldr->triList;

#ifdef CHECK_DUPLICATE
    bldr->hashTable = pfuNewHTable(HASHSIZE, sizeof(pfVec3), NULL);
#endif

    /* builder is empty */
    bldr->numTris			= 0;
    bldr->numIndexedTris		= 0;

    bldr->numPoints			= 0;
    bldr->numIndexedPoints		= 0;

    bldr->numLines			= 0;
    bldr->numIndexedLines		= 0;

    bldr->numLineStrips			= 0;
    bldr->numIndexedLineStrips		= 0;

    bldr->numFlatLineStrips		= 0;
    bldr->numIndexedFlatLineStrips	= 0;

    /* don't generate face colors if face or vertex colors are unspecified */
    bldr->autoColors  = PFDGBLDR_COLORS_PRESERVE;

    /* generate face normals if face or vertex normals are unspecified */
    bldr->autoNormals = PFDGBLDR_NORMALS_MISSING;

    /* don't modify texture coordinate specifications */
    bldr->autoTexture = PFDGBLDR_TEXTURE_PRESERVE;

    /* reverse vertex ordering based on computed and supplied normals */
    bldr->autoOrient  = PFDGBLDR_ORIENT_VERTICES;

    /* form triangle meshes from geosets */
    bldr->mesh = TRUE;

    /* collapse per_vertex or per_prim colors/normals */
    bldr->collapse = TRUE;

    /* maximum number of primitives processed as one chunk */
    bldr->limit = 20000;

    /* share index lists between per vertex geoset attributes when possible */
    bldr->shareIndexLists = TRUE;

    return bldr;    
}

void
pfdDelGeoBldr(pfdGeoBuilder *bldr)
{
    if (bldr != NULL)
    {
	int i;
	int n;

#ifdef	OLD_STYLE_FREE
	freeTris(bldr);
#endif

	if (bldr->mallocList != NULL)
	{
	    n = pfGetNum(bldr->mallocList);
	    for (i=0; i<n; i++)
		pfFree(pfGet(bldr->mallocList,i));
	    pfDelete(bldr->mallocList);
	}

#ifdef CHECK_ALLOCATIONS
	if (bldr->mallocSizeList != NULL)
	{
	    static int freed = 0;
	    int prevFreed = freed;
	    int n = pfGetNum(bldr->mallocSizeList);
	    for (i=0;i<n;i++)
		freed += (long)pfGet(bldr->mallocSizeList,i);
	    pfNotify(PFNFY_DEBUG,PFNFY_PRINT,
		"Freed %f MB",(freed-prevFreed)/(1024.0*1024.0));
	    pfNotify(PFNFY_DEBUG,PFNFY_PRINT,
		"Total Freed %f MB",freed/(1024.0*1024.0));
	    pfDelete(bldr->mallocSizeList);
	}
#endif

#ifdef CHECK_DUPLICATE
	pfuDelHTable(bldr->hashTable);
#endif

	pfDelete(bldr->gsetList);
	pfFree(bldr);
    }
}

void
pfdGeoBldrMode(pfdGeoBuilder *bldr, int mode, int val)
{
    switch (mode) 
    {
    case PFDGBLDR_AUTO_NORMALS:
	bldr->autoNormals = val;
	break;    

    case PFDGBLDR_AUTO_COLORS:
	bldr->autoColors = val;
	break;    
    
    case PFDGBLDR_AUTO_TEXTURE:
	bldr->autoTexture = val;
	break;    

    case PFDGBLDR_AUTO_ORIENT:
	bldr->autoOrient = val;
	break;    

    case PFDGBLDR_MESH_ENABLE:
	bldr->mesh = val;
	break;
    
    case PFDGBLDR_COLLAPSE_ATTRS:
	bldr->collapse = val;
	break;    

    case PFDGBLDR_BUILD_LIMIT:
	bldr->limit = val;
	break;    

    case PFDGBLDR_SHARE_INDEX_LISTS:
	bldr->shareIndexLists = val;
	break;    

    default:
	pfNotify(PFNFY_WARN, PFNFY_USAGE, 
	    "pfdGeoBldrMode() unknown mode %d", mode);
	break;
    }
}

int
pfdGetGeoBldrMode(pfdGeoBuilder *bldr, int mode)
{
    switch (mode) 
    {
    case PFDGBLDR_AUTO_NORMALS:
	return bldr->autoNormals;

    case PFDGBLDR_AUTO_COLORS:
	return bldr->autoColors;

    case PFDGBLDR_AUTO_TEXTURE:
	return bldr->autoTexture;

    case PFDGBLDR_AUTO_ORIENT:
	return bldr->autoOrient;

    case PFDGBLDR_MESH_ENABLE:
	return bldr->mesh;

    case PFDGBLDR_COLLAPSE_ATTRS:
	return bldr->collapse;

    case PFDGBLDR_BUILD_LIMIT:
	return bldr->limit;

    case PFDGBLDR_SHARE_INDEX_LISTS:
	return bldr->shareIndexLists;

    default:
	pfNotify(PFNFY_WARN, PFNFY_USAGE, 
	    "pfdGetGeoBldrMode() unknown mode %d", mode);
	return -1;
    }
}

int
pfdGetNumTris(pfdGeoBuilder *bldr)
{
    return bldr->numTris + bldr->numIndexedTris;
}

/*
 *	Yair: Compute the number of active texture units in a pfdGeom.
 */
void
pfdPreprocessGeomTextureUnits (pfdGeom *geom)
{
    int k;

    geom->numTextures = 0;

    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
    {
	if (geom->tbind[k] == PFGS_PER_VERTEX)
	    geom->numTextures = k + 1;
	else
	    return;
    }
}

void
pfdPreprocessPrimTextureUnits (pfdPrim *prim)
{
    int k;

    prim->numTextures = 0;

    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
    {
	if (prim->tbind[k] == PFGS_PER_VERTEX)
	    prim->numTextures = k + 1;
	else
	    return;
    }
}
/*
 * add an arbitrary pfdGeom to a pfdGeoBuilder
 */
void
pfdAddGeom(pfdGeoBuilder *bldr, pfdGeom *pb, int numGeom)
{
    pfdPrim tri = {0};
    int i, j, k;

    pfdPreprocessGeomTextureUnits (pb);

    if (!(pb->flags & PFD_INDEXED)) /* not indexed */
    {
	switch (pb->primtype) 
	{
 	case PFGS_POINTS:
	    pfdAddPoints(bldr, pb);
	    break;

	case PFGS_LINES:
	    pfdAddLines(bldr, pb);
	    break;

	case PFGS_LINESTRIPS:
	case PFGS_FLAT_LINESTRIPS:
	    pfdAddLineStrips(bldr, pb, numGeom);
	    break;

	case PFGS_TRIS:
	    tri.numTextures = pb->numTextures;
	    tri.nbind = pb->nbind;
	    tri.cbind = pb->cbind;
	    for (i = 0 ; i < PF_MAX_TEXTURES ; i ++)
		tri.tbind[i] = pb->tbind[i];
	    for (i = 0; i < pb->numVerts; ) 
	    {
		for (j = 0; j < 3; j++, i++) 
		{
		    /* position */
		    pfCopyVec3(tri.coords[j], pb->coords[i]);

		    /* color */
 		    if (tri.cbind == PFGS_PER_VERTEX)
			pfCopyVec4(tri.colors[j], pb->colors[i]);
		    else
		    if (tri.cbind == PFGS_PER_PRIM)
			pfCopyVec4(tri.colors[j], pb->colors[i/3]);
		    else 
		    if (tri.cbind == PFGS_OVERALL)
		    	pfCopyVec4(tri.colors[j], pb->colors[0]);

		    /* normal */
		    if (tri.nbind == PFGS_PER_VERTEX)
			pfCopyVec3(tri.norms[j], pb->norms[i]);
		    else
		    if (tri.nbind == PFGS_PER_PRIM)
			pfCopyVec3(tri.norms[j], pb->norms[i/3]);
		    else 
		    if (tri.nbind == PFGS_OVERALL)
			pfCopyVec3(tri.norms[j], pb->norms[0]);

		    /* texture */
		    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
			if (tri.tbind[k] == PFGS_PER_VERTEX)
			    pfCopyVec2(tri.texCoords[k][j], pb->texCoords[k][i]);
		}
		pfdAddTri(bldr, &tri);
	    }
	    break;

	case PFGS_POLYS:
	    pfdAddPoly(bldr, pb);
	    break;

   	default:
	    break;
	}
    }
    else 
    {
	switch(pb->primtype) 
	{
        case PFGS_POINTS:
            pfdAddIndexedPoints(bldr, pb);
            break;

        case PFGS_LINES:
            pfdAddIndexedLines(bldr, pb);
            break;

        case PFGS_LINESTRIPS:
        case PFGS_FLAT_LINESTRIPS:
            pfdAddIndexedLineStrips(bldr, pb, numGeom);
            break;

        case PFGS_TRIS:
	    tri.numTextures = pb->numTextures;
            tri.nbind = pb->nbind;
            tri.cbind = pb->cbind;
	    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
		tri.tbind[k] = pb->tbind[k];

            tri.coordList    = pb->coordList;
            tri.normList     = pb->normList;
            tri.colorList    = pb->colorList;

	    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
		tri.texCoordList[k] = pb->texCoordList[k];

            for (i = 0; i < pb->numVerts; ) 
	    {
                for (j = 0; j < 3; j++, i++) 
		{
		    /* position */
		    tri.icoords[j] = pb->icoords[i];

		    /* color */
                    if (tri.cbind == PFGS_PER_VERTEX)
			tri.icolors[j] = pb->icolors[i];
                    else
		    if (tri.cbind == PFGS_PER_PRIM)
			tri.icolors[j] = pb->icolors[i/3];
                    else 
		    if (tri.cbind == PFGS_OVERALL)
			tri.icolors[j] = pb->icolors[0];

		    /* normal */
                    if (tri.nbind == PFGS_PER_VERTEX)
			tri.inorms[j] = pb->inorms[i];
                    else
		    if (tri.cbind == PFGS_PER_PRIM)
			tri.inorms[j] = pb->inorms[i/3];
                    else 
		    if (tri.nbind == PFGS_OVERALL)
			tri.inorms[j] = pb->inorms[0];

		    /* texture */
		    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
			if (tri.tbind[k] == PFGS_PER_VERTEX)
			    tri.itexCoords[k][j] = pb->itexCoords[k][i];
                }
                pfdAddIndexedTri(bldr, &tri);
            }
            break;

        case PFGS_POLYS:
            pfdAddIndexedPoly(bldr, pb);
            break;

        default:
            break;
	}
    }
}

static pfdPrim *
getPrim(pfdGeoBuilder *bldr)
{
    pfdPrim	*t		= NULL;
    int		 oldChunkSize	= TRICHUNK;
    int		 newChunkSize	= 0;

    /* 
     * Create and link in a new chunk of triangles whose size is twice 
     * the size of the previous chunk. Use last triangle in chunk as link.
    */
    if (bldr->availTri->flags == LINK_TRI)
    {
    	if (bldr->availTri->next == bldr->triList)
	{
	    /* determine old chunk size */
	    t = bldr->triList;
	    while (&t[oldChunkSize-1] != bldr->availTri)
	    {
	        t = t[oldChunkSize-1].next;
	        oldChunkSize <<= 1;

		if (oldChunkSize > MAXTRICHUNK)
		    oldChunkSize >>= 1;
	    }

	    /* determine new chunk size */
	    newChunkSize = oldChunkSize << 1;

	    if (newChunkSize > MAXTRICHUNK)
		newChunkSize >>= 1;

#ifdef CHECK_ALLOCATIONS
	    {
		static int taTRIS = 0;

		pfNotify(PFNFY_DEBUG,PFNFY_PRINT,
		    "Allocated %d tris (%d bytes), total of %f MB",
		    newChunkSize,
		    sizeof(pfdPrim),
		    newChunkSize*sizeof(pfdPrim)/(1024.0*1024.0));

		taTRIS += newChunkSize;

		pfAdd(bldr->mallocSizeList,
		    (void *)(newChunkSize*sizeof(pfdPrim)));
	    	pfNotify(PFNFY_DEBUG,PFNFY_PRINT,
		    "Total Allocated tris = %d (Total MB: %f)",
		    taTRIS,
		    taTRIS*sizeof(pfdPrim)/(1024.*1024.));
	    }
#endif
	    bldr->availTri = t[oldChunkSize-1].next = 
		pfCalloc(newChunkSize, sizeof(pfdPrim), NULL);
	    pfAdd(bldr->mallocList,bldr->availTri);

	    /* Point last tri in chunk to head of linked list */
	    t = &bldr->availTri[newChunkSize-1];
	    t->next = bldr->triList;
	    t->flags = LINK_TRI;
	}
    	else
	    bldr->availTri = bldr->availTri->next;
    }

    t = bldr->availTri;
    bldr->availTri++;
    return t;
}

#ifdef	OLD_STYLE_FREE
static void
freeTris(pfdGeoBuilder *bldr)
{
    pfdPrim		*tri;
    int		chunkSize = TRICHUNK;
    static int FreedTris = 0;
    int prevFreedTris = FreedTris;

    /* Free linked list of triangle chunks */
    tri = bldr->triList;
    do
    {
	pfdPrim		*next;

	next = tri[chunkSize-1].next;
	pfFree(tri);
	FreedTris++;
	tri = next;

	chunkSize <<= 1;

	if (chunkSize > MAXTRICHUNK)
	    chunkSize >>= 1;
    } while (tri != bldr->triList);

    fprintf(stderr, "Freed another %d tris for a total of %d\n",
	FreedTris-prevFreedTris, FreedTris);
}
#endif

/* ###################################################################### */
/* #                                                                    # */
/* #       Add a pfdGeom of specified type to a pfdGeoBuilder           # */
/* #                                                                    # */
/* ###################################################################### */

void
pfdAddPoly(pfdGeoBuilder *bldr, pfdGeom *polygon)
{
    if (polygon == NULL)
    {
	pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddPoly() NULL polygon.");
	return;
    }

    if (polygon->numVerts < 3)
    {
	pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddPoly() "
	    "Degenerate polygon has %d vertices", polygon->numVerts);
	return;
    }

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessGeomTextureUnits (polygon);
    polygon->flags = 0;
    addPoly(bldr, polygon);
}

void
pfdAddIndexedPoly(pfdGeoBuilder *bldr, pfdGeom *polygon)
{
    int		t;

    if (polygon == NULL)
    {
	pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddIndexedPoly() NULL polygon.");
	return;
    }

    if (polygon->numVerts < 3)
    {
	pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddIndexedPoly() "
	    "Degenerate polygon has %d vertices", polygon->numVerts);
	return;
    }

    polygon->flags = PFD_INDEXED;
    
    bldr->coordList = polygon->coordList;
    bldr->normList = polygon->normList;
    bldr->colorList = polygon->colorList;
    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	bldr->texCoordList[t] = polygon->texCoordList[t];

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessGeomTextureUnits (polygon);
    addPoly(bldr, polygon);
}

void
pfdAddPoints(pfdGeoBuilder *bldr, pfdGeom *points)
{
    if (points == NULL)
    {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddPoints() NULL Points.");
        return;
    }

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessGeomTextureUnits (points);
    points->flags = 0;
    addPoints(bldr, points);
}

void
pfdAddIndexedPoints(pfdGeoBuilder *bldr, pfdGeom *points)
{
    int		t;

    if (points == NULL)
    {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddIndexedPoints() NULL Points.");
        return;
    }

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessGeomTextureUnits (points);

    points->flags = PFD_INDEXED;

    bldr->coordList = points->coordList;
    bldr->normList = points->normList;
    bldr->colorList = points->colorList;
    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	bldr->texCoordList[t] = points->texCoordList[t];

    addPoints(bldr, points);
}

void
pfdAddLines(pfdGeoBuilder *bldr, pfdGeom *lines)
{
    if (lines == NULL)
    {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddLines() NULL lines.");
        return;
    }

    if (lines->numVerts < 2)
    {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddLines() "
            "Degenerate line has %d vertices", lines->numVerts);
        return;
    }

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessGeomTextureUnits (lines);

    lines->flags = 0;
    addLines(bldr, lines);
}

void
pfdAddIndexedLines(pfdGeoBuilder *bldr, pfdGeom *lines)
{
    int		t; 

    if (lines == NULL)
    {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddIndexedLines() NULL Lines.");
        return;
    }

    if (lines->numVerts < 2)
    {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddIndexedLines() "
            "Degenerate line has %d vertices", lines->numVerts);
        return;
    }

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessGeomTextureUnits (lines);

    lines->flags = PFD_INDEXED;

    bldr->coordList = lines->coordList;
    bldr->normList = lines->normList;
    bldr->colorList = lines->colorList;
    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	bldr->texCoordList[t] = lines->texCoordList[t];

    addLines(bldr, lines);
}

void
pfdAddLineStrips(pfdGeoBuilder *bldr, pfdGeom *strip, int num)
{
    pfdGeom *tmp;
    int i;

    if (strip == NULL)
    {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddLines() NULL lines.");
        return;
    }

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessGeomTextureUnits (strip);

    for (i=0, tmp=strip; i< num; i++, tmp=tmp->next) {
    if (tmp->numVerts < 2)
    {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddLines() "
            "Degenerate line has %d vertices", tmp->numVerts);
        return;
    }

    tmp->flags = 0;
    }

    addLineStrips(bldr, strip, num);
}

/* user can't delete strip->coordList/normList/colorList/texCoordList 
 * because we do not duplicate them in the builder bldr 
 */
void
pfdAddIndexedLineStrips(pfdGeoBuilder *bldr, pfdGeom *strip, int num)
{
    pfdGeom *tmp;
    int i, t;

    for (i=0, tmp=strip; i<num; i++, tmp=tmp->next)
    {
    if (tmp == NULL)
    {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddIndexedLines() NULL Lines.");
        return;
    }

    if (tmp->numVerts < 2)
    {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddIndexedLines() "
            "Degenerate line has %d vertices", tmp->numVerts);
        return;
    }

    tmp->flags = PFD_INDEXED;
    }

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessGeomTextureUnits (strip);

    bldr->coordList = strip->coordList;
    bldr->normList = strip->normList;
    bldr->colorList = strip->colorList;
    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	bldr->texCoordList[t] = strip->texCoordList[t];

    addLineStrips(bldr, strip, num);
}

/* ###################################################################### */
/* #                                                                    # */
/* #       Add a pfdPrim of specified type to a pfdGeoBuilder           # */
/* #                                                                    # */
/* ###################################################################### */

void 
pfdAddPoint(pfdGeoBuilder *bldr, pfdPrim *point)
{
    pfdPrim *p;

    if (point == NULL)
    {
	pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddPoint() NULL point.");
	return;
    }

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessPrimTextureUnits (point);

    point->flags = 0;

    p = getPrim(bldr);
    copyPrim(point, p);

    addPoint(bldr, p);
}

void
pfdAddLine(pfdGeoBuilder *bldr, pfdPrim *line)
{
    pfdPrim *l;

    if (line == NULL)
    {
        pfNotify(PFNFY_WARN, PFNFY_USAGE, "pfdAddLine() NULL line.");
        return;
    }

    /* Do not add line if it is a point */
    if (pfEqualVec3(line->coords[0], line->coords[1]))
	return;

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessPrimTextureUnits (line);

    line->flags = 0;

    l = getPrim(bldr);
    copyPrim(line, l);

    addLine(bldr, l);
}

void
pfdAddTri(pfdGeoBuilder *bldr, pfdPrim *t)
{
    pfdPrim *tri;

    if (bldr == NULL || t == NULL)
	return;

    /* Do not add tri if any two verts are the same */
    if (pfEqualVec3(t->coords[0], t->coords[1]) || 
	pfEqualVec3(t->coords[1], t->coords[2]) || 
	pfEqualVec3(t->coords[2], t->coords[0]))
	return;

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessPrimTextureUnits (t);

    t->flags = 0;

    tri = getPrim(bldr);
    copyPrim(tri, t);

    addTri(bldr, tri);
}

void
pfdAddIndexedTri(pfdGeoBuilder *bldr, pfdPrim *t)
{
    pfdPrim	*tri;
    pfVec3	*c;
    int		k;

    if (bldr == NULL || t == NULL)
	return;

    c = t->coordList;

    /* Do not add tri if any two verts are the same */
    if (pfEqualVec3(c[t->icoords[0]], c[t->icoords[1]]) || 
	pfEqualVec3(c[t->icoords[1]], c[t->icoords[2]]) || 
	pfEqualVec3(c[t->icoords[2]], c[t->icoords[0]]))
	return;

    /*
     *	Yair: Compute number of active texture units on input polygon.
     */
    pfdPreprocessPrimTextureUnits (t);

    t->flags = PFD_INDEXED;

    bldr->coordList = t->coordList;
    bldr->normList = t->normList;
    bldr->colorList = t->colorList;
    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
	bldr->texCoordList[k] = t->texCoordList[k];

    tri = getPrim(bldr);
    *tri = *t;

    addIndexedTri(bldr, tri);
}

/* ###################################################################### */
/* #                                                                    # */
/* #      Private helper functions to add points to a pfdGeoBuilder     # */
/* #                                                                    # */
/* ###################################################################### */

static void
addPoint(pfdGeoBuilder *bldr, pfdPrim *point)
{
    pfdPrim 	*tmp;
    int 	 i;
    int 	 np;

    pfdPreprocessPrimTextureUnits(point);

    if (point->cbind == PFGS_PER_PRIM)
	point->cbind = PFGS_PER_VERTEX;
    if (point->nbind == PFGS_PER_PRIM)
	point->nbind = PFGS_PER_VERTEX;

    np = bldr->pointCounts[point->nbind][point->cbind][point->numTextures];

    /* Add point to appropriate geoset bin */
    tmp = bldr->pointBins[point->nbind][point->cbind][point->numTextures];

    /* sort by width */
    if (np == 0 || (tmp->pixelsize <= point->pixelsize)) 
    {
        bldr->pointBins[point->nbind][point->cbind][point->numTextures] = point;
        point->next = tmp;
    }
    else
    if (np == 1)
    {
	point->next = tmp->next;
	tmp->next = point;
    }
    else
    {
	/* sort by width */
        for (i=np; i>1; i--) 
	{
            if (tmp->next->pixelsize > point->pixelsize) 
                tmp = tmp->next;
	    else
		break;
        }
        point->next = tmp->next;
        tmp->next = point;
    }
    bldr->pointCounts[point->nbind][point->cbind][point->numTextures]++;
    bldr->numPoints++;
}

static void
addIndexedPoint(pfdGeoBuilder *bldr, pfdPrim *point)
{
    pfdPrim *tmp;
    int i;

    pfdPreprocessPrimTextureUnits(point);

    if (point->cbind == PFGS_PER_PRIM)
	point->cbind = PFGS_PER_VERTEX;
    if (point->nbind == PFGS_PER_PRIM)
	point->nbind = PFGS_PER_VERTEX;

    /* Add point to appropriate geoset bin */
    tmp = bldr->indexedPointBins[point->nbind][point->cbind][point->numTextures];

    /* sort by point size */
    if ((bldr->indexedPointCounts[point->nbind][point->cbind][point->numTextures]==0) ||
	(tmp->pixelsize <= point->pixelsize)) 
    {
        bldr->indexedPointBins[point->nbind][point->cbind][point->numTextures] = point;
        point->next = tmp;
    }
    else
    if (bldr->indexedPointCounts[point->nbind][point->cbind][point->numTextures]==1)
    {
	point->next = tmp->next;
	tmp->next = point;
    }
    else
    {
        for (i=bldr->indexedPointCounts[point->nbind][point->cbind][point->numTextures]; i>1; i--) 
	{
	    /* sort by point size */
            if (tmp->next->pixelsize > point->pixelsize) 
                tmp = tmp->next;
	    else
		break;
        }
        point->next = tmp->next;
        tmp->next = point;
    }

    bldr->indexedPointCounts[point->nbind][point->cbind][point->numTextures]++;
    bldr->numIndexedPoints++;
}

static void
addPoints(pfdGeoBuilder *bldr, pfdGeom *points)
{
    pfdPrim	*tri;
    pfdPrim	*triList;
    int 	 i;

    pfdPreprocessGeomTextureUnits(points);

    /* build up linked list of points */
    triList = NULL;
    for (i=0; i<points->numVerts; i++)
    {
        tri = getPrim(bldr);
        tri->next = triList;
        triList = tri;
    }

    /* fill in triList with points */
    tri = triList;
    for (i=0; i<points->numVerts; i++)
    {
        copyGeom(tri, points, i, 0, 0);
        tri->pixelsize = points->pixelsize; 
        tri = tri->next;
    }

    while (triList)
    {
        pfdPrim *next;

        next = triList->next;
        if (points->flags & PFD_INDEXED)
            addIndexedPoint(bldr, triList);
        else
            addPoint(bldr, triList);
        triList = next;
    }
}

/* ###################################################################### */
/* #                                                                    # */
/* #      Private helper functions to add lines to a pfdGeoBuilder      # */
/* #                                                                    # */
/* ###################################################################### */

static void
addLine(pfdGeoBuilder *bldr, pfdPrim *line)
{
    pfdPrim *tmp;
    int i;

    pfdPreprocessPrimTextureUnits(line);

    /* Add line to appropriate geoset bin */
    tmp = bldr->lineBins[line->nbind][line->cbind][line->numTextures];

    /* sort by width */
    if ((bldr->lineCounts[line->nbind][line->cbind][line->numTextures]==0) 
	|| (tmp->pixelsize <= line->pixelsize))
    {
        bldr->lineBins[line->nbind][line->cbind][line->numTextures] = line;
        line->next = tmp;
    }
    else
    if (bldr->lineCounts[line->nbind][line->cbind][line->numTextures]==1)
    {
	line->next = tmp->next;
	tmp->next = line;
    }
    else
    {
	/* sort by width */
        for (i=bldr->lineCounts[line->nbind][line->cbind][line->numTextures]; i>1; i--) 
	{
            if (tmp->next->pixelsize > line->pixelsize) 
                tmp = tmp->next;
	    else
		break;
        }
        line->next = tmp->next;
        tmp->next = line;
    }

    bldr->lineCounts[line->nbind][line->cbind][line->numTextures]++;
    bldr->numLines++;
}

static void
addIndexedLine(pfdGeoBuilder *bldr, pfdPrim *line)
{
    pfdPrim *tmp;
    int i;

    pfdPreprocessPrimTextureUnits(line);

    /* Add line to appropriate geoset bin */
    tmp = bldr->indexedLineBins[line->nbind][line->cbind][line->numTextures];
    if ((bldr->indexedLineCounts[line->nbind][line->cbind][line->numTextures]==0) ||
	(tmp->pixelsize <= line->pixelsize)) /* sort by width */
    {
	bldr->indexedLineBins[line->nbind][line->cbind][line->numTextures] = line;
	line->next = tmp;
    }
    else
    if (bldr->indexedLineCounts[line->nbind][line->cbind][line->numTextures]==1)
    {
	line->next = tmp;
	tmp->next = line;
    }
    else 
    {
    	for (i=bldr->indexedLineCounts[line->nbind][line->cbind][line->numTextures]; i>1; i--) 
	{
	    /* sort by width */
	    if (tmp->next->pixelsize > line->pixelsize) 
	    	tmp = tmp->next;
	    else
		break;
	}
	line->next = tmp->next;
	tmp->next = line;
    }

    bldr->indexedLineCounts[line->nbind][line->cbind][line->numTextures]++;
    bldr->numIndexedLines++;
}

static void
addLines(pfdGeoBuilder *bldr, pfdGeom *lines)
{
    pfdPrim	*triList, *tri;
    int		i;
    int		numVerts = lines->numVerts;
    pfdGeom	*dup = NULL;

    if (lines->numVerts < 2)
	return;

    pfdPreprocessGeomTextureUnits(lines);
	
#ifdef CHECK_DUPLICATE_LINES
#ifdef CHECK_DUPLICATE
    for (i=0; i<lines->numVerts; i++)
    {
	pfuHashElt  he;

	if (lines->flags & PFD_INDEXED)
	    he.data = (void*)lines->coordList[lines->icoords[i]];
	else
	    he.data = (void*)lines->coords[i];

	/* Remove duplicate vertex */
	if (pfuEnterHash(bldr->hashTable, &he))
	{
	    int    j, k;

	    /* make a copy of entire pfdGeom before first modification */
	    if (dup == NULL)
	    {
		dup = pfdNewGeom(lines->numVerts);
		pfdCopyGeom(dup, lines);
	    }

	    lines->numVerts--;
	    for (j=i; j<lines->numVerts; j++)
	    {
		if (lines->flags & PFD_INDEXED)
		{
		    lines->icoords[j] = lines->icoords[j+1];

		    if (lines->nbind == PFGS_PER_VERTEX)
			lines->inorms[j] = lines->inorms[j+1];

		    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
			if (lines->tbind[k] == PFGS_PER_VERTEX)
			    lines->itexCoords[k][j] = lines->itexCoords[k][j+1];

		    if (lines->cbind == PFGS_PER_VERTEX)
			lines->icolors[j] = lines->icolors[j+1];
		}
		else
		{
		    pfCopyVec3(lines->coords[j], lines->coords[j+1]);

		    if (lines->nbind == PFGS_PER_VERTEX)
			pfCopyVec3(lines->norms[j], lines->norms[j+1]);

		    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
			if (lines->tbind[k] == PFGS_PER_VERTEX)
			    pfCopyVec2(lines->texCoords[k][j], 
						    lines->texCoords[k][j+1]);

		    if (lines->cbind == PFGS_PER_VERTEX)
			pfCopyVec4(lines->colors[j], lines->colors[j+1]);
		}
	    }
	    i--;
	}
    }
    pfuResetHTable(bldr->hashTable);
#endif
#endif

    if (lines->numVerts < 2)
    {
	/* restore original pfdGeom */
	pfdCopyGeom(lines, dup);
	pfdDelGeom(dup);
	return;
    }
	
    /* Build up linked list of lines */
    triList = NULL;
    for (i=0; i<lines->numVerts/2; i++)
    {
	tri = getPrim(bldr);
	tri->next = triList;
	triList = tri;
    }

    /* Fill in triList with lines */
    tri = triList;
    for (i=0; i<lines->numVerts; i=i+2)
    {
	copyGeom(tri, lines, i, i+1, 0);
	tri->pixelsize = lines->pixelsize; 
	tri = tri->next;
    }

    while (triList) 
    { 
	pfdPrim *next;

	next = triList->next;
	if (lines->flags & PFD_INDEXED)
	    addIndexedLine(bldr, triList);
	else
	    addLine(bldr, triList);
	triList = next;
    }

    if (dup != NULL)
    {
	/* restore original pfdGeom */
	pfdCopyGeom(lines, dup);
	pfdDelGeom(dup);
	return;
    }
}

static void
addLineStrip(pfdGeoBuilder *bldr, pfdGeom *strip)
{
    pfdGeom *tmp;
    int i;

    pfdPreprocessGeomTextureUnits(strip);

    /* Add line to appropriate geoset bin */
    if (strip->primtype & PFGS_LINESTRIPS) 
    {
    	tmp = bldr->linestripBins[strip->nbind][strip->cbind][strip->numTextures];

	/* sort by width */
    	if ((bldr->linestripCounts[strip->nbind][strip->cbind][strip->numTextures]==0) ||
		(tmp->pixelsize <= strip->pixelsize)) 
    	{
        	bldr->linestripBins[strip->nbind][strip->cbind][strip->numTextures] = strip;
        	strip->next = tmp;
    	}
    	else
	if (bldr->linestripCounts[strip->nbind][strip->cbind][strip->numTextures] == 1)
	{
		strip->next = tmp->next;
	    	tmp->next = strip;
	}
	else
    	{
	    for (i=bldr->linestripCounts[strip->nbind][strip->cbind][strip->numTextures]; i>1; i--) 
	    {
		/* sort by width */
		if (tmp->next->pixelsize > strip->pixelsize) 
		    tmp = tmp->next;
		else
		    break;
	    }
	    strip->next = tmp->next;
	    tmp->next = strip;
    	}
	
    	bldr->linestripCounts[strip->nbind][strip->cbind][strip->numTextures]++;
    	bldr->numLines += strip->numVerts-1;
    }
    else 
    {
    	tmp = bldr->flatlinestripBins[strip->nbind][strip->cbind][strip->numTextures];

	/* sort by width */
    	if ((bldr->flatlinestripCounts[strip->nbind][strip->cbind][strip->numTextures]==0) ||
	    (tmp->pixelsize <= strip->pixelsize)) 
    	{
	    bldr->flatlinestripBins[strip->nbind][strip->cbind][strip->numTextures] = strip;
	    strip->next = tmp;
    	}
    	else
	if (bldr->flatlinestripCounts[strip->nbind][strip->cbind][strip->numTextures] == 1)
	{
	    strip->next = tmp;
	    tmp->next = strip;
	}
	else
    	{
	    for (i=bldr->flatlinestripCounts[strip->nbind][strip->cbind][strip->numTextures]; i>1; i--) 
	    {
		/* sort by width */
            	if (tmp->next->pixelsize > strip->pixelsize) 
                	tmp = tmp->next;
		else
		    break;
	    }
	    strip->next = tmp->next;
	    tmp->next = strip;
    	}
	
    	bldr->flatlinestripCounts[strip->nbind][strip->cbind][strip->numTextures]++;
    	bldr->numLines += strip->numVerts-1;
    }
}

static void
addIndexedLineStrip(pfdGeoBuilder *bldr, pfdGeom *strip)
{
    pfdGeom *tmp;
    int i;

    pfdPreprocessGeomTextureUnits(strip);

    /* Add line to appropriate geoset bin */
    if (strip->primtype & PFGS_LINESTRIPS) 
    {
    	tmp = bldr->indexedlinestripBins[strip->nbind][strip->cbind][strip->numTextures];

	/* sort by width */
    	if ((bldr->indexedlinestripCounts[strip->nbind][strip->cbind][strip->numTextures]==0) ||
	    (tmp->pixelsize <= strip->pixelsize)) 
    	{
	    bldr->indexedlinestripBins[strip->nbind][strip->cbind][strip->numTextures] = strip;
	    strip->next = tmp;
    	}
    	else 
	if (bldr->indexedlinestripCounts[strip->nbind][strip->cbind][strip->numTextures] == 1)
	{
	    strip->next = tmp;
	    tmp->next = strip;
	}
	else
    	{
	    for (i=bldr->indexedlinestripCounts[strip->nbind][strip->cbind][strip->numTextures]; i>1; i--) 
	    {
		/* sort by width */
            	if (tmp->next->pixelsize > strip->pixelsize) 
		    tmp = tmp->next;
		else
		    break;
	    }
	    strip->next = tmp->next;
	    tmp->next = strip;
    	}
	
    	bldr->indexedlinestripCounts[strip->nbind][strip->cbind][strip->numTextures]++;
    	bldr->numIndexedLines += strip->numVerts-1;
    }
    else
    {        
	tmp = bldr->indexedflatlinestripBins[strip->nbind][strip->cbind][strip->numTextures];

	/* sort by width */
        if ((bldr->indexedflatlinestripCounts[strip->nbind][strip->cbind][strip->numTextures]==0)||
		(tmp->pixelsize <= strip->pixelsize)) 
        {
	    bldr->indexedflatlinestripBins[strip->nbind][strip->cbind][strip->numTextures] = strip;
	    strip->next = tmp;
        }
        else
	if (bldr->indexedflatlinestripCounts[strip->nbind][strip->cbind][strip->numTextures] == 1)
	{
	    strip->next = tmp;
	    tmp->next = strip;
	}
	else
        {
	    for (i=bldr->indexedflatlinestripCounts[strip->nbind][strip->cbind][strip->numTextures]; i>1; i--) 
	    {
		/* sort by width */
                if (tmp->next->pixelsize > strip->pixelsize) 
		    tmp = tmp->next;
		else
		    break;
	    }
	    strip->next = tmp->next;
	    tmp->next = strip;
        }

        bldr->indexedflatlinestripCounts[strip->nbind][strip->cbind][strip->numTextures]++;
        bldr->numIndexedLines += strip->numVerts-1;
    }
}

static void
addLineStrips(pfdGeoBuilder *bldr, pfdGeom *strip, int num)
{
    pfdGeom	*tmp, *tmp2;
    int		i;

    pfdPreprocessGeomTextureUnits(strip);

    /* all line strips are either indexed or regular */
    tmp = (pfdGeom *)pfdNewGeom(strip->numVerts);
    copyLineStrip(tmp, strip);
    if (tmp->flags & PFD_INDEXED)
    {
	addIndexedLineStrip(bldr, tmp);
    	for (i=1; i<num; i++) 
	{
	    tmp2 = tmp->next;
    	    tmp = (pfdGeom *)pfdNewGeom(tmp2->numVerts);
	    copyLineStrip(tmp, tmp2);
	    addIndexedLineStrip(bldr, tmp);
	}
    }
    else
    {
        addLineStrip(bldr, tmp);
	for (i=1; i<num; i++) 
	{
	    tmp2 = tmp->next;
    	    tmp = (pfdGeom *)pfdNewGeom(tmp2->numVerts);
	    copyLineStrip(tmp, tmp2);
	    addLineStrip(bldr, tmp);
	}
    }
}

/* ###################################################################### */
/* #                                                                    # */
/* #    Private helper functions to add polygons to a pfdGeoBuilder     # */
/* #                                                                    # */
/* ###################################################################### */

static void
addTri(pfdGeoBuilder *bldr, pfdPrim *tri)
{
    pfdPrim *tmp;

    pfdPreprocessPrimTextureUnits(tri);

    /* Add tri to appropriate geoset bin */
    tmp = bldr->triBins[tri->nbind][tri->cbind][tri->numTextures];

    if (bldr->triCounts[tri->nbind][tri->cbind][tri->numTextures]==0) 
    {
     	bldr->triBins[tri->nbind][tri->cbind][tri->numTextures] = tri;
     	tri->next = tmp;
    }
    else 
    {	
	tri->next = tmp->next;
	tmp->next = tri;
    }

    bldr->triCounts[tri->nbind][tri->cbind][tri->numTextures]++;
    bldr->numTris++;
}

static void
addIndexedTri(pfdGeoBuilder *bldr, pfdPrim *tri)
{
    pfdPrim *tmp;

    pfdPreprocessPrimTextureUnits(tri);

    /* Add tri to appropriate geoset bin */
    tmp = bldr->indexedTriBins[tri->nbind][tri->cbind][tri->numTextures];

    if (bldr->indexedTriCounts[tri->nbind][tri->cbind][tri->numTextures]==0)
    {
        bldr->indexedTriBins[tri->nbind][tri->cbind][tri->numTextures] = tri;
        tri->next = tmp;
    }
    else
    {
        tri->next = tmp->next;
        tmp->next = tri;
    }

    bldr->indexedTriCounts[tri->nbind][tri->cbind][tri->numTextures]++;
    bldr->numIndexedTris++;
}

static void
addPoly(pfdGeoBuilder *bldr, pfdGeom *polygon)
{
    pfdPrim	*triList, *tri;
    int		i, ntris;
    pfdGeom	*dup = NULL;
    int		k;
    
    if (polygon->numVerts < 3)
	return;

    pfdPreprocessGeomTextureUnits(polygon);

#ifdef CHECK_DUPLICATE
    for (i=0; i<polygon->numVerts; i++)
    {
	pfuHashElt  		he;

	if (polygon->flags & PFD_INDEXED)
	    he.data = (void*)polygon->coordList[polygon->icoords[i]];
	else
	    he.data = (void*)polygon->coords[i];

	/* Remove duplicate vertex */
	if (pfuEnterHash(bldr->hashTable, &he))
	{
	    int    j;

	    /* make a copy of entire pfdGeom before first modification */
	    if (dup == NULL)
	    {
		dup = pfdNewGeom(polygon->numVerts);
		pfdCopyGeom(dup, polygon);
	    }

	    polygon->numVerts--;
	    for (j=i; j<polygon->numVerts; j++)
	    {
		if (polygon->flags & PFD_INDEXED)
		{
		    polygon->icoords[j] = polygon->icoords[j+1];

		    if (polygon->nbind == PFGS_PER_VERTEX)
			polygon->inorms[j] = polygon->inorms[j+1];

		    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
			if (polygon->tbind[k] == PFGS_PER_VERTEX)
			    polygon->itexCoords[k][j] = 
					polygon->itexCoords[k][j+1];

		    if (polygon->cbind == PFGS_PER_VERTEX)
			polygon->icolors[j] = polygon->icolors[j+1];
		}
		else
		{
		    pfCopyVec3(polygon->coords[j], polygon->coords[j+1]);

		    if (polygon->nbind == PFGS_PER_VERTEX)
			pfCopyVec3(polygon->norms[j], polygon->norms[j+1]);

		    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
			if (polygon->tbind[k] == PFGS_PER_VERTEX)
			    pfCopyVec2(polygon->texCoords[k][j], 
				       polygon->texCoords[k][j+1]);

		    if (polygon->cbind == PFGS_PER_VERTEX)
			pfCopyVec4(polygon->colors[j], polygon->colors[j+1]);
		}
	    }
	    i--;
	}
    }
    pfuResetHTable(bldr->hashTable);
#endif

    if (polygon->numVerts < 3)
    {
	/* restore original pfdGeom */
	pfdCopyGeom(polygon, dup);
	pfdDelGeom(dup);
	return;
    }

    /* Build up linked list of triangles */
    triList = NULL;
    for (i=0; i<polygon->numVerts-2; i++)
    {
	tri = getPrim(bldr);
	tri->next = triList;
	triList = tri;
    }

    /* Fill in triList with decomposed 'polygon' */
    ntris = triangulatePoly(polygon, triList);

    for (i=0; i<ntris; i++)
    { 
	pfdPrim *next;

	next = triList->next;
	if (polygon->flags & PFD_INDEXED)
	    addIndexedTri(bldr, triList);
	else
	    addTri(bldr, triList);
	triList = next;
    }

    /* restore original pfdGeom */
    if (dup != NULL)
    {
	pfdCopyGeom(polygon, dup);
	pfdDelGeom(dup);
    }
}

/* ###################################################################### */
/* #                                                                    # */
/* #                      pfdGeom Functions                             # */
/* #                                                                    # */
/* ###################################################################### */

/*
 * allocate and initialize a new pfdGeom structure data
 */
pfdGeom *
pfdNewGeom(int numV)
{
    pfdGeom *p = NULL;
    void *arena = pfGetSharedArena();
    int		i;
    int		t;

    /* set default primitive buffer size if needed */
    if (numV < 1)
	numV = 256;

    /* allocate main buffer structure */
    p = (pfdGeom *)pfCalloc(1, sizeof(pfdGeom), arena);

    /* initialize scalar data */
    p->flags	  = 0;
    p->nbind      = 0;
    p->cbind      = 0;
    for (i = 0 ; i < PF_MAX_TEXTURES ; i ++)
	p->tbind[i]      = 0;
    p->numVerts   = numV;
    p->primtype   = 0;
    p->pixelsize  = 0.0f;
    p->next       = NULL;

    /* allocate arrays of length numV for each buffer component */
    p->coords     = (pfVec3 *)pfCalloc(numV, sizeof(pfVec3), arena);
    p->norms      = (pfVec3 *)pfCalloc(numV, sizeof(pfVec3), arena);
    p->colors     = (pfVec4 *)pfCalloc(numV, sizeof(pfVec4), arena);
    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	p->texCoords[t]  = (pfVec2 *)pfCalloc(numV, sizeof(pfVec2), arena);

    p->coordList    = NULL;
    p->normList     = NULL;
    p->colorList    = NULL;
    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	p->texCoordList[t] = NULL;

    p->icoords    = (unsigned short *)pfCalloc(numV, sizeof(unsigned short), arena);
    p->inorms     = (unsigned short *)pfCalloc(numV, sizeof(unsigned short), arena);
    p->icolors    = (unsigned short *)pfCalloc(numV, sizeof(unsigned short), arena);
    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	p->itexCoords[t] = (unsigned short *)pfCalloc(numV, sizeof(unsigned short), arena);

    /* return address of new pfdGeom */
    return p;
}

/*
 * resize internal pfdGeom data (keep old values intact where possible)
 */
void
pfdResizeGeom(pfdGeom *p, int numV)
{
    int	t;

    /* bail out if given a null pointer */
    if (p == NULL) 
	return;

    /* set default primitive buffer size if needed */
    if (numV < 1)
	numV = 256;
    
    /* no action needed if old and new sizes are the same */
    if (p->numVerts == numV)
	return;

    /* reallocate each buffer component array to length numV */
    p->coords     = (pfVec3 *)pfRealloc(p->coords,     numV*sizeof(pfVec3));
    p->norms      = (pfVec3 *)pfRealloc(p->norms,      numV*sizeof(pfVec3));
    p->colors     = (pfVec4 *)pfRealloc(p->colors,     numV*sizeof(pfVec4));
    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	p->texCoords[t]  = (pfVec2 *)pfRealloc(
				p->texCoords[t],  numV*sizeof(pfVec2));

    p->icoords    = (unsigned short *)pfRealloc(p->icoords,    numV*sizeof(unsigned short));
    p->inorms     = (unsigned short *)pfRealloc(p->inorms,     numV*sizeof(unsigned short));
    p->icolors    = (unsigned short *)pfRealloc(p->icolors,    numV*sizeof(unsigned short));
    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	p->itexCoords[t] = (unsigned short *)pfRealloc(
				p->itexCoords[t], numV*sizeof(unsigned short));

    /* update array-size information */
    p->numVerts   = numV;
}

/*
 * delete pfdGeom structure
 */
void
pfdDelGeom(pfdGeom *p)
{
    int		t;

    if (p != NULL) 
    {
	if (p->coords     != NULL) pfFree(p->coords    );
	if (p->norms      != NULL) pfFree(p->norms     );
	if (p->colors     != NULL) pfFree(p->colors    );
	for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	    if (p->texCoords[t]  != NULL) pfFree(p->texCoords[t] );

	if (p->icoords    != NULL) pfFree(p->icoords   );
	if (p->inorms     != NULL) pfFree(p->inorms    );
	if (p->icolors    != NULL) pfFree(p->icolors   );
	for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	    if (p->itexCoords[t] != NULL) pfFree(p->itexCoords[t]);

	pfFree(p);
    }
}

/*
 * copy pfdGeom contents
 */
void
pfdCopyGeom(pfdGeom *dst, pfdGeom *src)
{
    int v;
    int	i;

    /* check arguments */
    if (dst == NULL || src == NULL)
	return;
    if (dst == src)
	return;
    
#if 0
    /* *** NO, LET'S ASK THE USER TO PROVIDE A BIG-ENOUGH DEST *** */
    /* make sure destination pfdGeom is big enough */
    if (dst->numVerts < src->numVerts)
	pfdResizeGeom(dst, src->numVerts);
#endif

    /* perform the copy */
    dst -> numTextures = src -> numTextures;
    dst->flags	    = src->flags;
    dst->nbind      = src->nbind;
    dst->cbind      = src->cbind;
    for (i = 0 ; i < PF_MAX_TEXTURES ; i ++)
	dst->tbind[i]      = src->tbind[i];
    dst->numVerts   = src->numVerts;
    dst->primtype   = src->primtype;
    dst->pixelsize  = src->pixelsize;
    dst->next       = src->next;

    dst->coordList    = src->coordList;
    dst->normList     = src->normList;
    dst->colorList    = src->colorList;
    for (i = 0 ; i < PF_MAX_TEXTURES ; i ++)
	dst->texCoordList[i] = src->texCoordList[i];

    for (v = 0; v < src->numVerts; v++)
    {
	pfCopyVec3(dst->coords[v],    src->coords[v]);
	pfCopyVec3(dst->norms[v],     src->norms[v]);
	pfCopyVec4(dst->colors[v],    src->colors[v]);
	for (i = 0 ; i < PF_MAX_TEXTURES ; i ++)
	    if (dst->texCoords[i] && src->texCoords[i])
		pfCopyVec2(dst->texCoords[i][v], src->texCoords[i][v]);

	dst->icoords[v]    = src->icoords[v];
	dst->inorms[v]     = src->inorms[v];
	dst->icolors[v]    = src->icolors[v];
	for (i = 0 ; i < PF_MAX_TEXTURES ; i ++)
	    if(src->itexCoords[i] && dst->itexCoords[i])
	        dst->itexCoords[i][v] = src->itexCoords[i][v];
    }
}

/* 
 * reverse polygon vertex order (CW -> CCW)
 */
int
pfdReverseGeom(pfdGeom *polygon)
{
    int          i;
    int          j;
    int		 k;
    int		 half;

    /* can only reverse existing data */
    if (polygon == NULL)
	return 0;

    /* can only deal with facets */
    switch (polygon->primtype)
    {
    case PFGS_POINTS:
    case PFGS_LINES:
    case PFGS_LINESTRIPS:
    case PFGS_FLAT_LINESTRIPS:
	return 0;

    case PFGS_TRIS:
    case PFGS_QUADS:
    case PFGS_TRISTRIPS:
    case PFGS_FLAT_TRISTRIPS:
    case PFGS_POLYS:
	break;
    }

    half = polygon->numVerts/2;

    /* reverse vertex data */
    for (i = 0; i < half; i++)
    {
        pfVec3 temp;
        j = polygon->numVerts-i-1;
        pfCopyVec3(temp, polygon->coords[i]);
        pfCopyVec3(polygon->coords[i], polygon->coords[j]);
        pfCopyVec3(polygon->coords[j], temp);
    }

    /* reverse per-vertex normal data (if supplied) */
    if (polygon->nbind == PFGS_PER_VERTEX)
        for (i = 0; i < half; i++)
        {
            pfVec3 temp;
            j = polygon->numVerts-i-1;
            pfCopyVec3(temp, polygon->norms[i]);
            pfCopyVec3(polygon->norms[i], polygon->norms[j]);
            pfCopyVec3(polygon->norms[j], temp);
        }

    /* reverse per-vertex color data (if supplied) */
    if (polygon->cbind == PFGS_PER_VERTEX)
        for (i = 0; i < half; i++)
        {
            pfVec4 temp;
            j = polygon->numVerts-i-1;
            pfCopyVec4(temp, polygon->colors[i]);
            pfCopyVec4(polygon->colors[i], polygon->colors[j]);
            pfCopyVec4(polygon->colors[j], temp);
        }

    /* reverse per-vertex texture coordinates (if supplied) */
    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
	if (polygon->tbind[k] == PFGS_PER_VERTEX)
	    for (i = 0; i < half; i++)
	    {
		pfVec2 temp;
		j = polygon->numVerts-i-1;
		pfCopyVec2(temp, polygon->texCoords[k][i]);
		pfCopyVec2(polygon->texCoords[k][i], polygon->texCoords[k][j]);
		pfCopyVec2(polygon->texCoords[k][j], temp);
	    }
 
    return 1;
}

/* 
 * Orient polygons so their vertex ordering matches the supplied normals. 
 */
int
pfdOrientGeom(pfdGeom *polygon, int orientMode)
{
    int 	 i;
    int 	 j;
    int		 reversed;
    float	 xi, xj;
    float	 yi, yj;
    float	 zi, zj;
    pfVec3	 supplied;
    pfVec3	 computed;
#ifdef	HOPE_FOR_THE_BEST
    pfVec3	 edge0; 
    pfVec3	 edge1;	
#endif

    /* can only deal with valid data */
    if (polygon == NULL)
	return 0;

    /* test for known method of orientation */
    if ((orientMode != PFDGBLDR_ORIENT_NORMALS ) &&
        (orientMode != PFDGBLDR_ORIENT_VERTICES))
	return 0;

    /* check primitive type */
    switch (polygon->primtype)
    {
    /* can only deal with facets */
    case PFGS_POINTS:
    case PFGS_LINES:
    case PFGS_LINESTRIPS:
    case PFGS_FLAT_LINESTRIPS:
	return 0;

    /* line strips are difficult */
    case PFGS_TRISTRIPS:
    case PFGS_FLAT_TRISTRIPS:
	return 0;

    case PFGS_TRIS:
    case PFGS_QUADS:
    case PFGS_POLYS:
	break;
    }

    /* can only deal with three or more vertices */
    if (polygon->numVerts < 3)
	return 0;

    /* get average normal (may be unnormalized) */
    switch (polygon->nbind)
    {
    case PFGS_PER_VERTEX:
	/* average all supplied normals */
	pfCopyVec3(supplied, polygon->norms[0]);
	for (i = 1; i < polygon->numVerts; i++)
	    pfAddVec3(supplied, supplied, polygon->norms[i]);
	break;

    case PFGS_PER_PRIM:
    case PFGS_OVERALL:
	/* use the supplied normal */
	pfCopyVec3(supplied, polygon->norms[0]);
	break;
    
    default:
	/* can only perform reorientation if normals are supplied */
	return 0;
	break;
    }
    
#ifdef	HOPE_FOR_THE_BEST
    /* form (unnormalized) normal using first three vertices */
    pfSubVec3(edge0, polygon->coords[1], polygon->coords[0]);
    pfSubVec3(edge1, polygon->coords[2], polygon->coords[0]);
    pfCrossVec3(computed, edge0, edge1);
#else
    /* form (unnormalized) normal using Newell's projected area */
    pfSetVec3(computed, 0.0f, 0.0f, 0.0f);
    for (i = 0; i < polygon->numVerts; i++)
    {
	j = (i + 1) % polygon->numVerts;

	xi = polygon->coords[i][PF_X];
	xj = polygon->coords[j][PF_X];
	yi = polygon->coords[i][PF_Y];
	yj = polygon->coords[j][PF_Y];
	zi = polygon->coords[i][PF_Z];
	zj = polygon->coords[j][PF_Z];

	computed[PF_X] += (yi - yj)*(zi + zj);
	computed[PF_Y] += (zi - zj)*(xi + xj);
	computed[PF_Z] += (xi - xj)*(yi + yj);
    }
#endif

    /* ordering ok if the dot product is non-negative */
    if (pfDotVec3(computed, supplied) >= 0.0f)
	return 0;

    if (orientMode == PFDGBLDR_ORIENT_NORMALS)
    {
	/* reverse normal data */
	switch (polygon->nbind)
	{
	case PFGS_PER_VERTEX:
	    for (i = 0; i < polygon->numVerts; i++)
		pfNegateVec3(polygon->norms[i], polygon->norms[i]);
	    break;

	case PFGS_PER_PRIM:
	case PFGS_OVERALL:
	    pfNegateVec3(polygon->norms[0], polygon->norms[0]);
	    break;
	}
	reversed = 1;
    }
    else
    if (orientMode == PFDGBLDR_ORIENT_VERTICES)
    {
	/* reverse vertex data */
	pfdReverseGeom(polygon);
	reversed = 1;
    }
    else
    {
	reversed = 0;
    }

    /* let caller know if pfdGeom was reversed */
    return reversed;
}

static void
copyGeom(pfdPrim *tri, pfdGeom *pgon, int v0, int v1, int v2)
{
    int		i, k, t;

    tri->numTextures = pgon->numTextures;
    tri->flags = pgon->flags;
    tri->nbind = pgon->nbind;
    tri->cbind = pgon->cbind;
    for (i = 0 ; i < PF_MAX_TEXTURES ; i ++)
	tri->tbind[i] = pgon->tbind[i];

    if (tri->flags & PFD_INDEXED)
    {
	tri->coordList = pgon->coordList;
	tri->normList = pgon->normList;
	tri->colorList = pgon->colorList;
        for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	    tri->texCoordList[t] = pgon->texCoordList[t];

	tri->icoords[0] = pgon->icoords[v0];
	tri->icoords[1] = pgon->icoords[v1];
	tri->icoords[2] = pgon->icoords[v2];

	switch (tri->nbind)
	{
	case PFGS_PER_VERTEX:
	    tri->inorms[0] = pgon->inorms[v0];
	    tri->inorms[1] = pgon->inorms[v1];
	    tri->inorms[2] = pgon->inorms[v2];
	    break;

	case PFGS_OVERALL:
	case PFGS_PER_PRIM:
	    tri->inorms[0] = pgon->inorms[0];
	    break;

	default:
	    break;
	}

	switch (tri->cbind)
	{
	case PFGS_PER_VERTEX:
	    tri->icolors[0] = pgon->icolors[v0];
	    tri->icolors[1] = pgon->icolors[v1];
	    tri->icolors[2] = pgon->icolors[v2];
	    break;

	case PFGS_OVERALL:
	case PFGS_PER_PRIM:
	    tri->icolors[0] = pgon->icolors[0];
	    break;

	default:
	    break;
	}

	for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
	    switch (tri->tbind[k])
	    {
		case PFGS_PER_VERTEX:
		    tri->itexCoords[k][0] = pgon->itexCoords[k][v0];
		    tri->itexCoords[k][1] = pgon->itexCoords[k][v1];
		    tri->itexCoords[k][2] = pgon->itexCoords[k][v2];
		    break;

		default:
		    break;
	    }
    } 
    else
    {
	pfCopyVec3(tri->coords[0], pgon->coords[v0]);
	pfCopyVec3(tri->coords[1], pgon->coords[v1]);
	pfCopyVec3(tri->coords[2], pgon->coords[v2]);

	switch (tri->nbind)
	{
	case PFGS_PER_VERTEX:
	    pfCopyVec3(tri->norms[0], pgon->norms[v0]);
	    pfCopyVec3(tri->norms[1], pgon->norms[v1]);
	    pfCopyVec3(tri->norms[2], pgon->norms[v2]);
	    break;

	case PFGS_OVERALL:
	case PFGS_PER_PRIM:
	    pfCopyVec3(tri->norms[0], pgon->norms[0]);
	    break;

	default:
	    break;
	}

	switch (tri->cbind)
	{
	case PFGS_PER_VERTEX:
	    pfCopyVec4(tri->colors[0], pgon->colors[v0]);
	    pfCopyVec4(tri->colors[1], pgon->colors[v1]);
	    pfCopyVec4(tri->colors[2], pgon->colors[v2]);
	    break;

	case PFGS_OVERALL:
	case PFGS_PER_PRIM:
	    pfCopyVec4(tri->colors[0], pgon->colors[0]);
	    break;

	default:
	    break;
	}

	for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
	    switch (tri->tbind[k])
	    {
		case PFGS_PER_VERTEX:
		    pfCopyVec2(tri->texCoords[k][0], pgon->texCoords[k][v0]);
		    pfCopyVec2(tri->texCoords[k][1], pgon->texCoords[k][v1]);
		    pfCopyVec2(tri->texCoords[k][2], pgon->texCoords[k][v2]);
		    break;

		default:
		break;
	    }

    }
}

/* ###################################################################### */
/* #                                                                    # */
/* #         Construct geosets from pfdGeoBuilder geometry              # */
/* #                                                                    # */
/* ###################################################################### */

static void
copyLineStrip(pfdGeom *dst, pfdGeom *src)
{
    int i, k, num;

    dst->numTextures = src->numTextures;
    num = dst->numVerts = src->numVerts;
    dst->nbind = src->nbind;
    dst->cbind = src->cbind;
    for (i = 0 ; i < PF_MAX_TEXTURES ; i ++)
	dst->tbind[i] = src->tbind[i];
    dst->primtype = src->primtype;
    dst->flags = src->flags;
    dst->pixelsize= src->pixelsize;
   
    if (src->flags & PFD_INDEXED)
    {
    	for (i=0; i<num; i++) 
    	{
	    dst->icoords[i] = src->icoords[i];
	}
	switch (src->cbind)
	{
	case PFGS_PER_VERTEX:
	    for (i=0; i<num-1; i++)
	    {
          	dst->icolors[i] = src->icolors[i];
	    }
	    if (src->primtype & PFGS_LINESTRIPS)
		dst->icolors[i] = src->icolors[i];
	    break;
	case PFGS_PER_PRIM:
	case PFGS_OVERALL:
	    dst->icolors[0] = src->icolors[0];
	    break;
	default:
	    break;
	}	
	switch (src->nbind)
	{
	case PFGS_PER_VERTEX:
	    for (i=0; i<num-1; i++)
            {
                dst->inorms[i] = src->inorms[i];
            }
            if (src->primtype & PFGS_LINESTRIPS)
                dst->inorms[i] = src->inorms[i];
            break;
        case PFGS_PER_PRIM:     
        case PFGS_OVERALL:      
            dst->inorms[0] = src->inorms[0];  
            break;      
        default:        
            break;      
        }       
	for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
	    switch (src->tbind[k])
	    {
		case PFGS_PER_VERTEX:
		    for (i=0; i<num; i++)
		    {
			dst->itexCoords[k][i] = src->itexCoords[k][i];
		    }
		    break;
		case PFGS_PER_PRIM:     
		case PFGS_OVERALL:      
		    dst->itexCoords[k][0] = src->itexCoords[k][0];
		    break;
		default:        
		    break;      
	    }  
    } 
    else
    {
        for (i=0; i<num; i++)
        {
           pfCopyVec3(dst->coords[i], src->coords[i]);
 	}
	switch (src->cbind)
	{
	case PFGS_PER_VERTEX:
	    for (i=0; i<num-1; i++)
	    {
		pfCopyVec4(dst->colors[i], src->colors[i]);
	    }
	    if (src->primtype & PFGS_LINESTRIPS)
		pfCopyVec4(dst->colors[i], src->colors[i]);
	    break;
	case PFGS_OVERALL:
        case PFGS_PER_PRIM:
	    pfCopyVec4(dst->colors[0], src->colors[0]);
	    break;
	default:
	    break;
	}
	switch (src->nbind)
	{
	case PFGS_PER_VERTEX:
	    for (i=0; i<num-1; i++)
            {
                pfCopyVec3(dst->norms[i], src->norms[i]);
            }
	    if (src->primtype & PFGS_LINESTRIPS)
		pfCopyVec3(dst->norms[i], src->norms[i]);
            break;
        case PFGS_OVERALL:
        case PFGS_PER_PRIM:
	    pfCopyVec3(dst->norms[0], src->norms[0]);
            break;
        default:
            break;
        }
	for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
	    switch (src->tbind[k])
	    {
		case PFGS_PER_VERTEX:
		    for (i=0; i<num; i++)
		    {
			pfCopyVec2(dst->texCoords[k][i], src->texCoords[k][i]);
		    }
		    break;
		case PFGS_OVERALL:
		case PFGS_PER_PRIM:
		    pfCopyVec2(dst->texCoords[k][0], src->texCoords[k][0]);
		    break;
		default:
		    break;
	    }
    }
}

void
copyPrim(pfdPrim *dst, pfdPrim *src)
{
    int i, j;

    dst->flags     = src->flags;
    dst->nbind     = src->nbind;
    dst->cbind     = src->cbind;
    for (i = 0 ; i < PF_MAX_TEXTURES ; i ++)
	dst->tbind[i]     = src->tbind[i];
    dst->next      = src->next;
    dst->pixelsize = src->pixelsize;
    dst->numTextures = src->numTextures;

    for (i = 0; i < 3; i++) {
    	pfCopyVec3(dst->coords[i], src->coords[i]);
    	pfCopyVec4(dst->colors[i], src->colors[i]);
    	pfCopyVec3(dst->norms[i], src->norms[i]);
	for (j = 0 ; j < PF_MAX_TEXTURES ; j ++)
	    if ((src -> tbind[j] == PFGS_PER_VERTEX) && 
		(dst->texCoords[j]) && 
		(src->texCoords[j]))
		pfCopyVec2(dst->texCoords[j][i], src->texCoords[j][i]);
    }
}

static void 
moveTriList(pfdGeoBuilder *ts, 
	    int nb1, int cb1, int numTex1, 
	    int nb2, int cb2, int numTex2)
{
    pfdPrim	    *tri;
    
    if ((nb1 == nb2) && (cb1 == cb2) && (numTex1 == numTex2))
	return;
	
    if (ts->triCounts[nb2][cb2][numTex2] == 0)
    {
	ts->triBins[nb2][cb2][numTex2] = ts->triBins[nb1][cb1][numTex1];
    }
    else
    {    
	tri = ts->triBins[nb2][cb2][numTex2];
	
	while(tri->next)
	    tri = tri->next;

	tri->next = ts->triBins[nb1][cb1][numTex1];
    }
    
    ts->triBins[nb1][cb1][numTex1] = NULL;
    ts->triCounts[nb2][cb2][numTex2] += ts->triCounts[nb1][cb1][numTex1];
    ts->triCounts[nb1][cb1][numTex1] = 0;

    return;
}

static void 
moveIndexedTriList(pfdGeoBuilder *ts, 
	    int nb1, int cb1, int numTex1, 
	    int nb2, int cb2, int numTex2)
{
    pfdPrim	    *tri;
    
    if ((nb1 == nb2) && (cb1 == cb2) && (numTex1 == numTex2))
	return;
 	
    if (ts->indexedTriCounts[nb2][cb2][numTex2] == 0)
    {
	ts->indexedTriBins[nb2][cb2][numTex2] = 
		ts->indexedTriBins[nb1][cb1][numTex1];
    }
    else
    {    
	tri = ts->indexedTriBins[nb2][cb2][numTex2];
	
	while(tri->next)
	    tri = tri->next;

	tri->next = ts->indexedTriBins[nb1][cb1][numTex1];
    }
    
    ts->indexedTriBins[nb1][cb1][numTex1] = NULL;
    ts->indexedTriCounts[nb2][cb2][numTex2] += 
	    ts->indexedTriCounts[nb1][cb1][numTex1];
    ts->indexedTriCounts[nb1][cb1][numTex1] = 0;
    return;
}

static void
collapseAttr(pfdGeoBuilder *bldr, pfdPrim *tri, int attr)
{
    pfdPrim		*t;
    float          	*val, *tval;
    int	    		success, collapseBind;

    if (!tri)
	return;

    t = tri;

    if (attr == PFGS_NORMAL3)
    {
	if (tri->nbind == PFGS_PER_VERTEX)
	{
	    collapseBind = PFGS_PER_PRIM;
	    success = 1;
	    if (tri->flags & PFD_INDEXED)
	    {
		while (tri && success)
		{
		    val = bldr->normList[tri->inorms[0]];
		    tval = bldr->normList[tri->inorms[1]];
		    if (PFALMOST_EQUAL_VEC3(val, tval, SORTOF))
		    {
			tval = bldr->normList[tri->inorms[2]];
			if (!PFALMOST_EQUAL_VEC3(val, tval, SORTOF))
			    success = 0;
		    }
		    else
			success = 0;
		    tri = tri->next;
		}
	    }
	    else
	    {
		while (tri && success)
		{
		    val = tri->norms[0];
		    tval = tri->norms[1];
		    if (PFALMOST_EQUAL_VEC3(val, tval, SORTOF))
		    {
			tval = tri->norms[2];
			if (!PFALMOST_EQUAL_VEC3(val, tval, SORTOF))
			    success = 0;
		    }
		    else
			success = 0;
		    tri = tri->next;
		}
	    }
	}
	else if (tri->nbind == PFGS_PER_PRIM)
	{
	    collapseBind = PFGS_OVERALL;
	    success = 1;
	    if (tri->flags & PFD_INDEXED)
	    {
		val = bldr->normList[tri->inorms[0]];
		tri = tri->next;
		while (tri && success)
		{
		    tval = bldr->normList[tri->inorms[0]];
		    if (!PFALMOST_EQUAL_VEC3(val, tval, SORTOF))
			success = 0;
		    tri = tri->next;
		}
	    }
	    else
	    {
		val = tri->norms[0];
		tri = tri->next;
		while (tri && success)
		{
		    tval = tri->norms[0];
		    if (!PFALMOST_EQUAL_VEC3(val, tval, SORTOF))
			success = 0;
		    tri = tri->next;
		}
	    }
	}
	if (success)
	{
	    if (t->flags & PFD_INDEXED)
		moveIndexedTriList(bldr, t->nbind, t->cbind, t->numTextures,
				   collapseBind, t->cbind, t->numTextures);
	    else
		moveTriList(bldr, t->nbind, t->cbind, t->numTextures,
			    collapseBind, t->cbind, t->numTextures);

	    /* Success, change binding type in first tris */
	    t->nbind = collapseBind;
	}
    }
    else if (attr == PFGS_COLOR4)
    {
	if (tri->cbind == PFGS_PER_VERTEX)
	{
	    collapseBind = PFGS_PER_PRIM;
	    success = 1;
	    if (tri->flags & PFD_INDEXED)
	    {
		while (tri && success)
		{
		    val = bldr->colorList[tri->icolors[0]];
		    tval = bldr->colorList[tri->icolors[1]];
		    if (PFALMOST_EQUAL_VEC4(val, tval, SORTOF))
		    {
			tval = bldr->colorList[tri->icolors[2]];
			if (!PFALMOST_EQUAL_VEC4(val, tval, SORTOF))
			    success = 0;
		    }
		    else
			success = 0;
		    tri = tri->next;
		}
	    }
	    else
	    {
		while (tri && success)
		{
		    val = tri->colors[0];
		    tval = tri->colors[1];
		    if (PFALMOST_EQUAL_VEC4(val, tval, SORTOF))
		    {
			tval = tri->colors[2];
			if (!PFALMOST_EQUAL_VEC4(val, tval, SORTOF))
			    success = 0;
		    }
		    else
			success = 0;
		    tri = tri->next;
		}
	    }
	}
	else if (tri->cbind == PFGS_PER_PRIM)
	{
	    collapseBind = PFGS_OVERALL;
	    success = 1;
	    if (tri->flags & PFD_INDEXED)
	    {
		val = bldr->colorList[tri->icolors[0]];
		tri = tri->next;
		while (tri && success)
		{
		    tval = bldr->colorList[tri->icolors[0]];
		    if (!PFALMOST_EQUAL_VEC4(val, tval, SORTOF))
			success = 0;
		    tri = tri->next;
		}
	    }
	    else
	    {
		val = tri->colors[0];
		tri = tri->next;
		while (tri && success)
		{
		    tval = tri->colors[0];
		    if (!PFALMOST_EQUAL_VEC4(val, tval, SORTOF))
			success = 0;
		    tri = tri->next;
		}
	    }
	}

	if (success)
	{
	    if (t->flags & PFD_INDEXED)
		moveIndexedTriList(bldr, t->nbind, t->cbind, t->numTextures,
				   t->nbind, collapseBind, t->numTextures);
	    else
		moveTriList(bldr, t->nbind, t->cbind, t->numTextures,
			    t->nbind, collapseBind, t->numTextures);

	    /* Success, change binding type in first tris */
	    t->cbind = collapseBind;
	}
    }
}

static void
freeStripBins(pfdGeom *bin[4][4][PF_MAX_TEXTURES + 1], int count[4][4][PF_MAX_TEXTURES + 1])
{
    int i, j, k;
    pfdGeom *t;

    for (i = 0; i < 4; i++)
    {
	for (j = 0; j < 4; j++)
	{
	    for (k = 0; k < PF_MAX_TEXTURES + 1 ; k++)
	    {
		while(count[i][j][k] > 0)
		{
		    t = bin[i][j][k]->next;
		    pfdDelGeom(bin[i][j][k]);
		    bin[i][j][k] = t;
		    count[i][j][k]--;
		}
	    }
	}
    }
}

const pfList*
pfdBuildGSets(pfdGeoBuilder *bldr)
{
    int             nbind;
    int             cbind;
    int             tbind;
    pfdPrim	    *tri;
    int		    k;

    /* make sure geoset list is empty */
    pfResetList(bldr->gsetList);

    /* 
     * Run through geoset bins and collapse attributes, e.g. - 
     * change binding from PER_VERTEX to PER_PRIM if 
     * all of the PER_VERTEX attributes of each tri are the same.
     */
    if (bldr->collapse)
    {
	/* Collapse color PER_VERTEX to PER_PRIM */
	cbind = PFGS_PER_VERTEX;
	for (nbind = 3; nbind >= 0; nbind--)
	    for (tbind = PF_MAX_TEXTURES ; tbind >= 0; tbind--)
	    {
		/* collapse non-indexed bin */
		tri = bldr->triBins[nbind][cbind][tbind];
		if (tri)
		    collapseAttr(bldr, tri, PFGS_COLOR4);

		/* collapse indexed bin */
		tri = bldr->indexedTriBins[nbind][cbind][tbind];
		if (tri)
		    collapseAttr(bldr, tri, PFGS_COLOR4);
	    }
	/* Collapse normal PER_VERTEX to PER_PRIM */
	nbind = PFGS_PER_VERTEX;
	for (cbind = 3; cbind >= 0; cbind--)
	    for (tbind = PF_MAX_TEXTURES ; tbind >= 0; tbind--)
	    {
		/* collapse non-indexed bin */
		tri = bldr->triBins[nbind][cbind][tbind];
		if (tri)
		    collapseAttr(bldr, tri, PFGS_NORMAL3);

		/* collapse indexed bin */
		tri = bldr->indexedTriBins[nbind][cbind][tbind];
		if (tri)
		    collapseAttr(bldr, tri, PFGS_NORMAL3);
	    }
	/* Collapse color PER_PRIM to OVERALL */
	cbind = PFGS_PER_PRIM;
	for (nbind = 3; nbind >= 0; nbind--)
	    for (tbind = PF_MAX_TEXTURES ; tbind >= 0; tbind--)
	    {
		/* collapse non-indexed bin */
		tri = bldr->triBins[nbind][cbind][tbind];
		if (tri)
		    collapseAttr(bldr, tri, PFGS_COLOR4);

		/* collapse indexed bin */
		tri = bldr->indexedTriBins[nbind][cbind][tbind];
		if (tri)
		    collapseAttr(bldr, tri, PFGS_COLOR4);
	    }
	/* Collapse normal PER_PRIM to OVERALL */
	nbind = PFGS_PER_PRIM;
	for (cbind = 3; cbind >= 0; cbind--)
	    for (tbind = PF_MAX_TEXTURES ; tbind >= 0; tbind--)
	    {
		/* collapse non-indexed bin */
		tri = bldr->triBins[nbind][cbind][tbind];
		if (tri)
		    collapseAttr(bldr, tri, PFGS_NORMAL3);

		/* collapse indexed bin */
		tri = bldr->indexedTriBins[nbind][cbind][tbind];
		if (tri)
		    collapseAttr(bldr, tri, PFGS_NORMAL3);
	    }
    }

    /* Run through bins again and create pfGeoSet(s) */
    for (nbind = 3; nbind >= 0; nbind--)
	for (cbind = 3; cbind >= 0; cbind--)
	    for (tbind = PF_MAX_TEXTURES ; tbind >= 0; tbind--)
		makeGSets(bldr, nbind, cbind, tbind);

    /* reset pfdGeoBuilder bins now that data is in geoset form */
    bzero(bldr->pointBins, sizeof(bldr->pointBins));
    bzero(bldr->pointCounts, sizeof(bldr->pointCounts));
    bldr->numPoints = 0;

    bzero(bldr->indexedPointBins, sizeof(bldr->indexedPointBins));
    bzero(bldr->indexedPointCounts, sizeof(bldr->indexedPointCounts));
    bldr->numIndexedPoints = 0;

    bzero(bldr->triBins, sizeof(bldr->triBins));
    bzero(bldr->triCounts, sizeof(bldr->triCounts));
    bldr->numTris = 0;

    bzero(bldr->indexedTriBins, sizeof(bldr->indexedTriBins));
    bzero(bldr->indexedTriCounts, sizeof(bldr->indexedTriCounts));
    bldr->numIndexedTris = 0;

    bzero(bldr->lineBins, sizeof(bldr->lineBins));
    bzero(bldr->lineCounts, sizeof(bldr->lineCounts));
    bldr->numLines = 0;

    bzero(bldr->indexedLineBins, sizeof(bldr->indexedLineBins));
    bzero(bldr->indexedLineCounts, sizeof(bldr->indexedLineCounts));
    bldr->numIndexedLines = 0;

    freeStripBins(bldr->linestripBins, bldr->linestripCounts);
    bzero(bldr->linestripBins, sizeof(bldr->linestripBins));
    bzero(bldr->linestripCounts, sizeof(bldr->linestripCounts));
    bldr->numLineStrips = 0;

    freeStripBins(bldr->indexedlinestripBins, bldr->indexedlinestripCounts);
    bzero(bldr->indexedlinestripBins, sizeof(bldr->indexedlinestripBins));
    bzero(bldr->indexedlinestripCounts, sizeof(bldr->indexedlinestripCounts));
    bldr->numIndexedLineStrips = 0;

    freeStripBins(bldr->flatlinestripBins, bldr->flatlinestripCounts);
    bzero(bldr->flatlinestripBins, sizeof(bldr->flatlinestripBins));
    bzero(bldr->flatlinestripCounts, sizeof(bldr->flatlinestripCounts));
    bldr->numFlatLineStrips = 0;

    freeStripBins(bldr->indexedflatlinestripBins, bldr->indexedflatlinestripCounts);
    bzero(bldr->indexedflatlinestripBins, sizeof(bldr->indexedflatlinestripBins));
    bzero(bldr->indexedflatlinestripCounts, sizeof(bldr->indexedflatlinestripCounts));
    bldr->numIndexedFlatLineStrips = 0;

    bldr->coordList    = NULL;
    bldr->normList     = NULL;
    bldr->colorList    = NULL;

    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
	bldr->texCoordList[k] = NULL;

    bldr->availTri = bldr->triList;

    /* return list of builder's pfGeoSets to caller */
    return bldr->gsetList;
}

/* ###################################################################### */
/* #                                                                    # */
/* #             Private pfGeoSet construction functions                # */
/* #                                                                    # */
/* ###################################################################### */

/*
 * makeTriGSets -- Make pfGeoSet(s) for non-indexed triangles
 */
static void
makeTriGSets(pfdGeoBuilder *bldr, int _nbind, int _cbind, int _tbind)
{
    int           i;
    int           j;
    int           k;
    int	    	  ntris;
    int	    	  totalTris;
    pfGeoSet     *gset		= NULL;
    pfdPrim      *t		= NULL;
    pfdPrim      *tri		= NULL;
    pfVec3       *coords	= NULL;
    pfVec3       *norms		= NULL;
    pfVec4       *colors	= NULL;
    pfVec2       *texCoords[PF_MAX_TEXTURES];

    if (bldr == NULL)
	return;

    for (i = 0 ; i < PF_MAX_TEXTURES ; i ++)
	texCoords[i] = NULL;

    tri       = bldr->triBins  [_nbind][_cbind][_tbind];
    totalTris = bldr->triCounts[_nbind][_cbind][_tbind];

    while (totalTris > 0)
    {

	int	nbind = _nbind;
	int	cbind = _cbind;
	int	tbind = _tbind;

	gset = pfNewGSet(bldr->sharedArena);

	/*
	 * note: limit geoset size to 20K triangles to ensure that attribute
	 * triangle mesher indices stay within 64k unsigned short limit. 
	 */
	ntris = PF_MIN2(totalTris, bldr->limit);

	pfGSetPrimType(gset, PFGS_TRIS);
	pfGSetNumPrims(gset, ntris);

	/* generate vertex position attribute array */
	{
	    coords = pfMalloc(sizeof(pfVec3) * 3 * ntris, bldr->sharedArena);
	    j = 0;
	    t = tri;
	    for (i = 0; i < ntris; i++)
	    {
		pfCopyVec3(coords[j++], t->coords[0]);
		pfCopyVec3(coords[j++], t->coords[1]);
		pfCopyVec3(coords[j++], t->coords[2]);

		t = t->next;
	    }
	    pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, coords, NULL);
	}

	/* generate color attribute array */
	if (bldr->autoColors == PFDGBLDR_COLORS_DISCARD)
	{
	    cbind = PFGS_OFF;
	    colors = NULL;
	}
	else
	if ((bldr->autoColors == PFDGBLDR_COLORS_MISSING && cbind == PFGS_OFF) ||
	    (bldr->autoColors == PFDGBLDR_COLORS_GENERATE))
	{
	    cbind = PFGS_OVERALL;
	    colors = pfMalloc(sizeof(pfVec4), bldr->sharedArena);
	    pfuRandomColor(colors[0], 0.5f, 1.0f);
	} 
	else
	{
	    switch (cbind)
	    {
	    case PFGS_PER_VERTEX:
		colors = pfMalloc(sizeof(pfVec4) * 3 * ntris, bldr->sharedArena);
		j = 0;
		t = tri;
		for (i = 0; i < ntris; i++)
		{
		    pfCopyVec4(colors[j++], t->colors[0]);
		    pfCopyVec4(colors[j++], t->colors[1]);
		    pfCopyVec4(colors[j++], t->colors[2]);
		    t = t->next;
		}
		break;

	    case PFGS_PER_PRIM:
		colors = pfMalloc(sizeof(pfVec4) * ntris, bldr->sharedArena);
		t = tri;
		for (i = 0; i < ntris; i++)
		{
		    pfCopyVec4(colors[i], t->colors[0]);
		    t = t->next;
		}
		break;

	    case PFGS_OVERALL:
		colors = pfMalloc(sizeof(pfVec4), bldr->sharedArena);
		pfCopyVec4(colors[0], tri->colors[0]);
		break;

	    case PFGS_OFF:
		colors = NULL;
		break;
	    }
	}
	pfGSetAttr(gset, PFGS_COLOR4, cbind, colors, NULL);

	/* generate texture attribute array */
	if (bldr->autoTexture == PFDGBLDR_TEXTURE_DISCARD)
	{
	    tbind = 0;
   	    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
		texCoords[k] = NULL;
	}
	else
	if ((bldr->autoTexture == PFDGBLDR_TEXTURE_MISSING && tbind == 0) ||
	    (bldr->autoTexture == PFDGBLDR_TEXTURE_GENERATE))
	{
	    static int firstTime = 1;
	    if (firstTime)
	    {
		firstTime = 0;
		pfNotify(PFNFY_WARN, PFNFY_PRINT, 
		    "makeGSets: geobuilder texgen not yet implemented");
	    }
	    tbind = 0;
   	    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
		texCoords[k] = NULL;
	} 
	else
	{
	    switch (tbind)
	    {
	    case 0:
		for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
		    texCoords[k] = NULL;
		break;
	    
	    default:
		tbind = _tbind;
		for (k = 0 ; k < tbind ; k ++)
		    texCoords[k] = pfMalloc(sizeof(pfVec2) * 3 * ntris, 
			bldr->sharedArena);
		j = 0;
		t = tri;
		for (i = 0; i < ntris; i++)
		{
		    for (k = 0 ; k < tbind ; k ++)
		    {
			pfCopyVec2(texCoords[k][j  ], t->texCoords[k][0]);
			pfCopyVec2(texCoords[k][j+1], t->texCoords[k][1]);
			pfCopyVec2(texCoords[k][j+2], t->texCoords[k][2]);
		    }
		    j += 3;
		    t = t->next;
		}
		break;
	    }
	}
  
	if (tbind == 0)
	    pfGSetAttr (gset, PFGS_TEXCOORD2, PFGS_OFF, NULL, NULL);
	else
	    for (k = 0 ; k < tbind ; k ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, k, PFGS_PER_VERTEX, 
				texCoords[k], NULL);

	/* generate normal attribute array */
	if (bldr->autoNormals == PFDGBLDR_NORMALS_DISCARD)
	{
	    nbind = PFGS_OFF;
	    norms = NULL;
	}
	else
	if ((bldr->autoNormals == PFDGBLDR_NORMALS_MISSING && nbind == PFGS_OFF) ||
	    (bldr->autoNormals == PFDGBLDR_NORMALS_GENERATE))
	{
	    nbind = PFGS_PER_PRIM;
	    norms = pfMalloc(sizeof(pfVec3) * ntris, bldr->sharedArena);
	    for (i = 0; i < ntris; i++)
	    {
		pfVec3          v10;
		pfVec3          v21;

		/* compute face normal */
		pfSubVec3(v10, coords[3*i + 1], coords[3*i + 0]);
		pfSubVec3(v21, coords[3*i + 2], coords[3*i + 1]);
		pfCrossVec3(norms[i], v10, v21);
		pfNormalizeVec3(norms[i]);
	    }
	}
	else
	{
	    switch (nbind)
	    {
	    case PFGS_PER_VERTEX:
		norms = pfMalloc(sizeof(pfVec3) * 3 * ntris, bldr->sharedArena);
		j = 0;
		t = tri;
		for (i = 0; i < ntris; i++)
		{
		    pfCopyVec3(norms[j++], t->norms[0]);
		    pfCopyVec3(norms[j++], t->norms[1]);
		    pfCopyVec3(norms[j++], t->norms[2]);
		    t = t->next;
		}
		break;

	    case PFGS_PER_PRIM:
		norms = pfMalloc(sizeof(pfVec3) * ntris, bldr->sharedArena);
		t = tri;
		for (i = 0; i < ntris; i++)
		{
		    pfCopyVec3(norms[i], t->norms[0]);
		    t = t->next;
		}
		break;

	    case PFGS_OVERALL:
		norms = pfMalloc(sizeof(pfVec3), bldr->sharedArena);
		pfCopyVec3(norms[0], tri->norms[0]);
		break;

	    case PFGS_OFF:
		norms = NULL;
		break;
	    }
	}
	pfGSetAttr(gset, PFGS_NORMAL3, nbind, norms, NULL);

	/* are normals consistent with CCW vertex interpretation */
	if ((norms != NULL) &&
	    ((bldr->autoOrient == PFDGBLDR_ORIENT_NORMALS) ||
	     (bldr->autoOrient == PFDGBLDR_ORIENT_VERTICES)))
	{
	    for (i = 0; i < ntris; i++)
	    {
		pfVec3	 supplied;
		pfVec3	 computed;
		pfVec3   v10;
		pfVec3   v21;

		/* get average (possibly unnormalized) normal vector */
		switch (nbind)
		{
		case PFGS_PER_VERTEX:
		    /* average all supplied normals */
		    pfCopyVec3(supplied,           norms[3*i    ]);
		    pfAddVec3 (supplied, supplied, norms[3*i + 1]);
		    pfAddVec3 (supplied, supplied, norms[3*i + 2]);
		    break;

		case PFGS_PER_PRIM:
		    /* use the supplied normal */
		    pfCopyVec3(supplied, norms[i]);
		    break;
		
		case PFGS_OVERALL:
		    /* use the supplied normal */
		    pfCopyVec3(supplied, norms[0]);
		    break;
		}
		
		/* compute geometric face normal from vertices*/
		pfSubVec3(v10, coords[3*i + 1], coords[3*i + 0]);
		pfSubVec3(v21, coords[3*i + 2], coords[3*i + 1]);
		pfCrossVec3(computed, v10, v21);

		/* ordering ok if the dot product is non-negative */
		if (pfDotVec3(computed, supplied) >= 0.0f)
		    continue;

		if (bldr->autoOrient == PFDGBLDR_ORIENT_NORMALS)
		{
		    /* reverse direction of vertex normals */
		    switch (nbind)
		    {
		    case PFGS_PER_VERTEX:
			pfNegateVec3(norms[3*i    ], norms[3*i    ]);
			pfNegateVec3(norms[3*i + 1], norms[3*i + 1]);
			pfNegateVec3(norms[3*i + 2], norms[3*i + 2]);
			break;

		    case PFGS_PER_PRIM:
			pfNegateVec3(norms[i], norms[i]);
			break;
		    
		    case PFGS_OVERALL:
			pfNegateVec3(norms[0], norms[0]);
			break;
		    }
		}
		else
		if (bldr->autoOrient == PFDGBLDR_ORIENT_VERTICES)
		{
		    /* reverse orientation (CW .vs CCW) of triangle */
		    {
			pfVec3 exchange;
			pfCopyVec3(exchange,           coords[3*i    ]);
			pfCopyVec3(coords[3*i    ],    coords[3*i + 2]);
			pfCopyVec3(coords[3*i + 2],    exchange);
		    }
		    if (cbind == PFGS_PER_VERTEX && colors != NULL)
		    {
			pfVec4 exchange;
			pfCopyVec4(exchange,           colors[3*i    ]);
			pfCopyVec4(colors[3*i    ],    colors[3*i + 2]);
			pfCopyVec4(colors[3*i + 2],    exchange);
		    }
		    if (nbind == PFGS_PER_VERTEX && norms != NULL)
		    {
			pfVec3 exchange;
			pfCopyVec3(exchange,           norms[3*i    ]);
			pfCopyVec3(norms[3*i    ],     norms[3*i + 2]);
			pfCopyVec3(norms[3*i + 2],     exchange);
		    }
		    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
			if (tbind > 0 && texCoords[k] != NULL)
			{
			    pfVec2 exchange;
			    pfCopyVec2(exchange,           
							texCoords[k][3*i    ]);
			    pfCopyVec2(texCoords[k][3*i    ], 
							texCoords[k][3*i + 2]);
			    pfCopyVec2(texCoords[k][3*i + 2], 
							exchange);
			}
		}
	    }
	}

	/* perform triangle strip generation */
	if (bldr->mesh)
	{
	    pfGeoSet *strip = NULL;
	    strip = pfdMeshGSet(gset);
	    if (strip != NULL)
	    {
		pfDelete(gset);
		gset = strip;
	    }
	}

	/* remember this geoset */
	pfAdd(bldr->gsetList, gset);
	totalTris -= ntris;

	/* update starting point in primitive chain */
	tri = t;
    }
}

/*
 * makeIndexedTriGSets -- Make pfGeoSet(s) for indexed triangles
 *
 *	Yair: The parameter _tbind holds the number of active texture units
 *	      for this bin.
 */
static void
makeIndexedTriGSets(pfdGeoBuilder *bldr, int _nbind, int _cbind, int _tbind)
{
    int           i;
    int           j;
    int		  k;
    int	    	  ntris;
    int	    	  totalTris;
    pfGeoSet     *gset		= NULL;
    pfdPrim      *t		= NULL;
    pfdPrim      *tri		= NULL;
    pfVec3       *coords	= NULL;
    pfVec3       *norms		= NULL;
    pfVec4       *colors	= NULL;
    pfVec2       *texCoords[PF_MAX_TEXTURES];
    ushort	 *icoords	= NULL;
    ushort	 *inorms	= NULL;
    ushort	 *icolors	= NULL;
    ushort	 *itexCoords[PF_MAX_TEXTURES];
    int icoords_share;

    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
    {
	texCoords[k] = NULL;
	itexCoords[k] = NULL;
    }

    tri       = bldr->indexedTriBins  [_nbind][_cbind][_tbind];
    totalTris = bldr->indexedTriCounts[_nbind][_cbind][_tbind];

    while (totalTris > 0)
    {
	int	nbind = _nbind;
	int	cbind = _cbind;
	int	tbind = _tbind;

	gset = pfNewGSet(bldr->sharedArena);

	/*
	 * Limit geoset size to 10K triangles to ensure that attribute
	 * triangle mesher indices stay within 32k unsigned short limit. 
	 */
	ntris = PF_MIN2(totalTris, bldr->limit);

	pfGSetPrimType(gset, PFGS_TRIS);
	pfGSetNumPrims(gset, ntris);

	/* generate vertex position attribute array */
	{
	    coords = bldr->coordList;

	    icoords = pfMalloc(sizeof(ushort) * 3 * ntris, 
						bldr->sharedArena);
	    j = 0;
	    t = tri;
	    for (i = 0; i < ntris; i++)
	    {
		icoords[j++] = t->icoords[0];
		icoords[j++] = t->icoords[1];
		icoords[j++] = t->icoords[2];
		t = t->next;
	    }
	}
	pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, coords, icoords);

	/* generate color attribute array */
	if (bldr->autoColors == PFDGBLDR_COLORS_DISCARD)
	{
	    cbind = PFGS_OFF;
	    colors = NULL;
	    icolors = NULL;
	}
	else
	if ((bldr->autoColors == PFDGBLDR_COLORS_MISSING && cbind == PFGS_OFF) ||
	    (bldr->autoColors == PFDGBLDR_COLORS_GENERATE))
	{
	    cbind = PFGS_OVERALL;

	    colors = pfMalloc(sizeof(pfVec4), bldr->sharedArena);
	    pfuRandomColor(colors[0], 0.5f, 1.0f);

	    icolors = pfMalloc(sizeof(ushort), bldr->sharedArena);
	    icolors[0] = 0;
	} 
	else
	{
	    switch (cbind)
	    {
	    case PFGS_PER_VERTEX:
		colors = bldr->colorList;
		j = 0;
		t = tri;
		icoords_share = bldr->shareIndexLists;
		for (i = 0; icoords_share && i < ntris; i++)
		{
		    if (icoords[j++] != t->icolors[0] ||
			icoords[j++] != t->icolors[1] ||
			icoords[j++] != t->icolors[2])
			icoords_share = FALSE;
		    t = t->next;
		}
		if (icoords_share)
		    icolors = icoords;
		else
		{
		    icolors = pfMalloc(sizeof(ushort) * 3 * ntris,
				       bldr->sharedArena);
		    j = 0;
		    t = tri;
		    for (i = 0; i < ntris; i++)
		    {
			icolors[j++] = t->icolors[0];
			icolors[j++] = t->icolors[1];
			icolors[j++] = t->icolors[2];
			t = t->next;
		    }
		}

		break;

	    case PFGS_PER_PRIM:
		colors = bldr->colorList;
		icolors = pfMalloc(sizeof(ushort) * ntris, bldr->sharedArena);
		t = tri;
		for (i = 0; i < ntris; i++)
		{
		    icolors[i] = t->icolors[0];
		    t = t->next;
		}
		break;

	    case PFGS_OVERALL:
		colors = bldr->colorList;
		icolors = pfMalloc(sizeof(ushort), bldr->sharedArena);
		icolors[0] = tri->icolors[0];
		break;

	    case PFGS_OFF:
		colors = NULL;
		icolors = NULL;
		break;
	    }
	}
	pfGSetAttr(gset, PFGS_COLOR4, cbind, colors, icolors);

	/* generate texture attribute array */
	if (bldr->autoTexture == PFDGBLDR_TEXTURE_DISCARD)
	{
	    tbind = 0;
	    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
	    {
		texCoords[k] = NULL;
		itexCoords[k] = NULL;
	    }
	}
	else
	if ((bldr->autoTexture == PFDGBLDR_TEXTURE_MISSING && tbind == 0) ||
	    (bldr->autoTexture == PFDGBLDR_TEXTURE_GENERATE))
	{
	    static int firstTime = 1;
	    if (firstTime)
	    {
		firstTime = 0;
		pfNotify(PFNFY_WARN, PFNFY_PRINT, 
		    "makeGSets: geobuilder texgen not yet implemented");
	    }
	    tbind = 0;
	    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
	    {
		texCoords[k] = NULL;
		itexCoords[k] = NULL;
	    }
	} 
	else
	{
	    switch (tbind)
	    {
	    case 0:
		for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
		{
		    texCoords[k] = NULL;
		    itexCoords[k] = NULL;
		}
		break;
	    
	    default:
		tbind = _tbind;
		for (k = 0 ; k < tbind ; k ++)
		    texCoords[k] = bldr->texCoordList[k];
		j = 0;
		t = tri;
		icoords_share = bldr->shareIndexLists;
		for (i = 0; icoords_share && i < ntris; i++)
		{
		    for (k = 0 ; k < tbind ; k ++)
			if (icoords[j] != t->itexCoords[k][0] ||
			    icoords[j+1] != t->itexCoords[k][1] ||
			    icoords[j+2] != t->itexCoords[k][2])
			    icoords_share = FALSE;

		    j += 3;
		    t = t->next;
		}
		if (icoords_share)
		    for (k = 0 ; k < tbind ; k ++)
			itexCoords[k] = icoords;
		else
		{
		    for (k = 0 ; k < tbind ; k ++)
			itexCoords[k] = pfMalloc(sizeof(ushort) * 3 * ntris,
					  bldr->sharedArena);
		    j = 0;
		    t = tri;
		    for (i = 0; i < ntris; i++)
		    {
			for (k = 0 ; k < tbind ; k ++)
			{
			    itexCoords[k][j  ] = t->itexCoords[k][0];
			    itexCoords[k][j+1] = t->itexCoords[k][1];
			    itexCoords[k][j+2] = t->itexCoords[k][2];
			}
			j += 3;
			t = t->next;
		    }
		}
		break;
	    }
	}

	if (tbind == 0)
	    pfGSetAttr(gset, PFGS_TEXCOORD2, PFGS_OFF, NULL, NULL);
	else
	    for (k = 0 ; k < tbind ; k ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, k, 
				PFGS_PER_VERTEX, texCoords[k], itexCoords[k]);

	/* generate normal attribute array */
	if (bldr->autoNormals == PFDGBLDR_NORMALS_DISCARD)
	{
	    nbind = PFGS_OFF;
	    norms = NULL;
	    inorms = NULL;
	}
	else
	if ((bldr->autoNormals == PFDGBLDR_NORMALS_MISSING && nbind == PFGS_OFF) ||
	    (bldr->autoNormals == PFDGBLDR_NORMALS_GENERATE))
	{
	    nbind = PFGS_PER_PRIM;
	    norms = pfMalloc(sizeof(pfVec3) * ntris, bldr->sharedArena);
	    inorms = pfMalloc(sizeof(ushort) * ntris, bldr->sharedArena);
	    for (i = 0; i < ntris; i++)
	    {
		pfVec3          v10;
		pfVec3          v21;

		/* compute face normal */
		pfSubVec3(v10, 
		    coords[icoords[3*i + 1]], 
		    coords[icoords[3*i + 0]]);
		pfSubVec3(v21, 
		    coords[icoords[3*i + 2]], 
		    coords[icoords[3*i + 1]]);
		pfCrossVec3(norms[i], v10, v21);
		pfNormalizeVec3(norms[i]);

		inorms[i] = i;
	    }
	}
	else
	{
	    switch (nbind)
	    {
	    case PFGS_PER_VERTEX:
		norms = bldr->normList;
		j = 0;
		t = tri;
		icoords_share = bldr->shareIndexLists;
		for (i = 0; icoords_share && i < ntris; i++)
		{
		    if (icoords[j++] != t->inorms[0] ||
			icoords[j++] != t->inorms[1] ||
			icoords[j++] != t->inorms[2])
			icoords_share = FALSE;
		    t = t->next;
		}
		if (icoords_share)
		    inorms = icoords;
		else
		{
		    inorms = pfMalloc(sizeof(ushort) * 3 * ntris,
				      bldr->sharedArena);
		    j = 0;
		    t = tri;
		    for (i = 0; i < ntris; i++)
		    {
			inorms[j++] = t->inorms[0];
			inorms[j++] = t->inorms[1];
			inorms[j++] = t->inorms[2];
			t = t->next;
		    }
		}
		break;

	    case PFGS_PER_PRIM:
		norms = bldr->normList;
		inorms = pfMalloc(sizeof(ushort) * ntris, bldr->sharedArena);
		t = tri;
		for (i = 0; i < ntris; i++)
		{
		    inorms[i] = t->inorms[0];
		    t = t->next;
		}
		break;

	    case PFGS_OVERALL:
		norms = bldr->normList;
		inorms = pfMalloc(sizeof(ushort), bldr->sharedArena);
		inorms[0] = tri->inorms[0];
		break;

	    case PFGS_OFF:
		norms = NULL;
		inorms = NULL;
		break;
	    }
	}
	pfGSetAttr(gset, PFGS_NORMAL3, nbind, norms, inorms);

	/* are normals consistent with CCW vertex interpretation */
	if ((norms != NULL && inorms != NULL) &&
	    ((bldr->autoOrient == PFDGBLDR_ORIENT_NORMALS) ||
	     (bldr->autoOrient == PFDGBLDR_ORIENT_VERTICES)))
	{
	    for (i = 0; i < ntris; i++)
	    {
		pfVec3	 supplied;
		pfVec3	 computed;
		pfVec3   v10;
		pfVec3   v21;

		/* get average (possibly unnormalized) normal vector */
		switch (nbind)
		{
		case PFGS_PER_VERTEX:
		    /* average all supplied normals */
		    pfCopyVec3(supplied,           norms[inorms[3*i    ]]);
		    pfAddVec3 (supplied, supplied, norms[inorms[3*i + 1]]);
		    pfAddVec3 (supplied, supplied, norms[inorms[3*i + 2]]);
		    break;

		case PFGS_PER_PRIM:
		    /* use the supplied normal */
		    pfCopyVec3(supplied, norms[inorms[i]]);
		    break;
		
		case PFGS_OVERALL:
		    /* use the supplied normal */
		    pfCopyVec3(supplied, norms[inorms[0]]);
		    break;
		}
		
		/* compute geometric face normal from vertices*/
		pfSubVec3(v10, 
		    coords[icoords[3*i + 1]], 
		    coords[icoords[3*i + 0]]);
		pfSubVec3(v21, 
		    coords[icoords[3*i + 2]], 
		    coords[icoords[3*i + 1]]);
		pfCrossVec3(computed, v10, v21);

		/* ordering ok if the dot product is non-negative */
		if (pfDotVec3(computed, supplied) >= 0.0f)
		    continue;

		if (bldr->autoOrient == PFDGBLDR_ORIENT_NORMALS)
		{
#if 0
		    /* reverse direction of vertex normals */
		    switch (nbind)
		    {
		    case PFGS_PER_VERTEX:
			pfNegateVec3(
			    norms[inorms[3*i    ]], 
			    norms[inorms[3*i    ]]);
			pfNegateVec3(
			    norms[inorms[3*i + 1]], 
			    norms[inorms[3*i + 1]]);
			pfNegateVec3(
			    norms[inorms[3*i + 2]], 
			    norms[inorms[3*i + 2]]);
			break;

		    case PFGS_PER_PRIM:
			pfNegateVec3(
			    norms[inorms[i]], 
			    norms[inorms[i]]);
			break;
		    
		    case PFGS_OVERALL:
			pfNegateVec3(
			    norms[inorms[0]], 
			    norms[inorms[0]]);
			break;
		    }
#else
		    static int firstTime = 1;
		    if (firstTime)
		    {
			firstTime = 0;
			pfNotify(PFNFY_WARN, PFNFY_PRINT, 
			    "makeGSets: it's too risky to modify indexed normals");
		    }
#endif
		}
		else
		if (bldr->autoOrient == PFDGBLDR_ORIENT_VERTICES)
		{
		    /* reverse orientation (CW .vs CCW) of triangle */
		    {
			ushort exchange;
			exchange         = icoords[3*i    ];
			icoords[3*i    ] = icoords[3*i + 2];
			icoords[3*i + 2] = exchange;
		    }
		    if (cbind == PFGS_PER_VERTEX && icolors != NULL &&
			icolors != icoords)
		    {
			ushort exchange;
			exchange         = icolors[3*i    ];
			icolors[3*i    ] = icolors[3*i + 2];
			icolors[3*i + 2] = exchange;
		    }
		    if (nbind == PFGS_PER_VERTEX && inorms != NULL &&
			inorms != icoords)
		    {
			ushort exchange;
			exchange         = inorms[3*i    ];
			inorms[3*i    ]  = inorms[3*i + 2];
			inorms[3*i + 2]  = exchange;
		    }

		    for (k = 0 ; k < PF_MAX_TEXTURES ; k ++)
			if (tbind > 0 && itexCoords[k] != NULL &&
			    itexCoords[k] != icoords)
			{
			    ushort exchange;
			    exchange             = itexCoords[k][3*i    ];
			    itexCoords[k][3*i    ]  = itexCoords[k][3*i + 2];
			    itexCoords[k][3*i + 2]  = exchange;
			}
		}
	    }
	}

	/* perform triangle strip generation */
	if (bldr->mesh)
	{
	    pfGeoSet *strip = NULL;
	    strip = pfdMeshGSet(gset);
	    if (strip != NULL)
	    {
		pfDelete(gset);
		gset = strip;
	    }
	}

	/* remember this geoset */
	pfAdd(bldr->gsetList, gset);
	totalTris -= ntris;

	/* update starting point in primitive chain */
	tri = t;
    }
}

/*
 * makeLineGSets -- Make pfGeoSet(s) for non-indexed lines and linestrips
 *
 * Yair: tbind holds the number of active texture units in this bin.
 */
static void
makeLineGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind)
{
    pfGeoSet     *gset;
    int           i, j, k, m, t;
    int	    	  totalLines, totalLineStrips;
    int	    	  numLines, numLineStrips, numLineStripVerts;
    int	   	 *lengthList;
    pfdPrim	 *lines, *l1;
    pfdGeom      *linestrips, *l2;

    pfVec3       *coords	= NULL;
    pfVec3       *norms		= NULL;
    pfVec4       *colors	= NULL;
    pfVec2       *texCoords[PF_MAX_TEXTURES];

    ushort	 *icoords	= NULL;
    ushort	 *inorms	= NULL;
    ushort	 *icolors	= NULL;
    ushort	 *itexCoords[PF_MAX_TEXTURES];

    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
    {
	texCoords[t] = NULL;
	itexCoords[t] = NULL;
    }
    
    lines           = bldr->lineBins       [nbind][cbind][tbind];
    totalLines      = bldr->lineCounts     [nbind][cbind][tbind];

    linestrips      = bldr->linestripBins  [nbind][cbind][tbind];
    totalLineStrips = bldr->linestripCounts[nbind][cbind][tbind];

    while ((totalLines!=0) || (totalLineStrips!=0)) 
    {
	l1 = lines;
	l2 = linestrips;
	if ((totalLines!=0) && ((totalLineStrips==0) ||
		(l1->pixelsize > l2->pixelsize)))  /* geoset for lines only */
	{
	gset = pfNewGSet(bldr->sharedArena);
	numLines = 1;
	while ((totalLines>1) && (l1->pixelsize==l1->next->pixelsize))
	{
	    numLines++;
	    l1 = l1->next;
	    totalLines--;
	}
	totalLines--;

	pfGSetPrimType(gset, PFGS_LINES);
	pfGSetNumPrims(gset, numLines);
        pfGSetLineWidth(gset, lines->pixelsize);


	switch (cbind)
	{
	case PFGS_PER_VERTEX:
	    colors = pfMalloc(sizeof(pfVec4) * 2 * numLines, bldr->sharedArena);
	    j = 0;
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		pfCopyVec4(colors[j++], l1->colors[0]);
		pfCopyVec4(colors[j++], l1->colors[1]);

		l1 = l1->next;
	    }
	    pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_VERTEX, colors, NULL);
	    break;

	case PFGS_PER_PRIM:
	    colors = pfMalloc(sizeof(pfVec4) * numLines, bldr->sharedArena);
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		pfCopyVec4(colors[i], l1->colors[0]);

		l1 = l1->next;
	    }
	    pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_PRIM, colors, NULL);
	    break;

	case PFGS_OVERALL:
	    colors = pfMalloc(sizeof(pfVec4), bldr->sharedArena);
	    pfCopyVec4(colors[0], lines->colors[0]);
	    pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, colors, NULL);
	    break;

	case PFGS_OFF:
	    pfGSetAttr(gset, PFGS_COLOR4, cbind, NULL, NULL);
	    break;
	}

	switch (nbind)
	{
	case PFGS_PER_VERTEX:
	    norms = pfMalloc(sizeof(pfVec3) * 2 * numLines, bldr->sharedArena);
	    j = 0;
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		pfCopyVec3(norms[j++], l1->norms[0]);
		pfCopyVec3(norms[j++], l1->norms[1]);

		l1 = l1->next;
	    }
	    pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX, norms, NULL);
	    break;

	case PFGS_PER_PRIM:
	    norms = pfMalloc(sizeof(pfVec3) * numLines, bldr->sharedArena);
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		pfCopyVec3(norms[i], l1->norms[0]);

		l1 = l1->next;
	    }
	    pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_PRIM, norms, NULL);
	    break;

	case PFGS_OVERALL:
	    norms = pfMalloc(sizeof(pfVec3), bldr->sharedArena);
	    pfCopyVec3(norms[0], lines->norms[0]);
	    pfGSetAttr(gset, PFGS_NORMAL3, PFGS_OVERALL, norms, NULL);
	    break;

	case PFGS_OFF:
	    pfGSetAttr(gset, PFGS_NORMAL3, nbind, NULL, NULL);
	    break;
	}

	if (tbind != 0)
	{
 	    for (t = 0 ; t < tbind ; t ++)
		texCoords[t] = pfMalloc(sizeof(pfVec2) * 2 * numLines, 
					bldr->sharedArena);
	    j = 0;
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		for (t = 0 ; t < tbind ; t ++)
		{
		    pfCopyVec2(texCoords[t][j  ], l1->texCoords[t][0]);
		    pfCopyVec2(texCoords[t][j+1], l1->texCoords[t][1]);
		}
		j += 2;
		l1 = l1->next;
	    }
 	    for (t = 0 ; t < tbind ; t ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, 
				PFGS_PER_VERTEX, texCoords[t], NULL);
	}
	{
	    coords = pfMalloc(sizeof(pfVec3) * 2 * numLines, bldr->sharedArena);
	    j = 0;
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		pfCopyVec3(coords[j++], l1->coords[0]);
		pfCopyVec3(coords[j++], l1->coords[1]);

		l1 = l1->next;
	    }
	    pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, coords, NULL);
	}

	/* perform line strip generation */
	if (bldr->mesh)
	{
	    pfGeoSet *strip = NULL;
	    strip = pfdMeshGSet(gset);
	    if (strip != NULL)
	    {
		pfDelete(gset);
		gset = strip;
	    }
	}

        pfAdd(bldr->gsetList, gset);
	lines = l1;
    }
    else if ((totalLineStrips!=0) && ((totalLines==0) ||
		(l1->pixelsize<l2->pixelsize))) /* line strips only */
    {
        gset = pfNewGSet(bldr->sharedArena);
        numLineStrips = 1;
	numLineStripVerts = l2->numVerts;
        while ((totalLineStrips>1) && (l2->pixelsize==l2->next->pixelsize))
        {
            numLineStrips++;
	    numLineStripVerts += l2->next->numVerts;
            l2 = l2->next;
	    totalLineStrips--;
        }
	totalLineStrips--;

        pfGSetPrimType(gset, PFGS_LINESTRIPS);
        pfGSetNumPrims(gset, numLineStrips);

	lengthList = (int *) pfMalloc( sizeof(int) * numLineStrips, bldr->sharedArena);
	pfGSetPrimLengths(gset, lengthList);
	for (k=0, l2=linestrips; k<numLineStrips; k++, l2=l2->next)
	{
	    lengthList[k] = l2->numVerts;
	}

	pfGSetLineWidth(gset, linestrips->pixelsize);

        switch (cbind)
        {
        case PFGS_PER_VERTEX:
            colors = pfMalloc(sizeof(pfVec4) * numLineStripVerts, bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
		for (m = 0; m < l2->numVerts; m++) {
                    pfCopyVec4(colors[j++], l2->colors[m]);
		}
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_VERTEX, colors, NULL);
            break;

        case PFGS_PER_PRIM:
            colors = pfMalloc(sizeof(pfVec4) * numLineStrips, bldr->sharedArena);
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                pfCopyVec4(colors[i], l2->colors[0]);
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_PRIM, colors, NULL);
            break;

        case PFGS_OVERALL:
            colors = pfMalloc(sizeof(pfVec4), bldr->sharedArena);
            pfCopyVec4(colors[0], linestrips->colors[0]);
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, colors, NULL);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_COLOR4, cbind, NULL, NULL);
            break;
        }

        switch (nbind)
        {
        case PFGS_PER_VERTEX:
            norms = pfMalloc(sizeof(pfVec3) * numLineStripVerts, bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts; m++) {
                    pfCopyVec3(norms[j++], l2->norms[m]);
		}
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX, norms, NULL);
            break;

        case PFGS_PER_PRIM:
            norms = pfMalloc(sizeof(pfVec3) * numLineStrips, bldr->sharedArena);
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                pfCopyVec3(norms[i], l2->norms[0]);
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_PRIM, norms, NULL);
            break;

        case PFGS_OVERALL:
            norms = pfMalloc(sizeof(pfVec3), bldr->sharedArena);
            pfCopyVec3(norms[0], linestrips->norms[0]);
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_OVERALL, norms, NULL);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_NORMAL3, nbind, NULL, NULL);
            break;
        }
        if (tbind != 0)
        {
 	    for (t = 0 ; t < tbind ; t ++)
		texCoords[t] = pfMalloc(sizeof(pfVec2) * numLineStripVerts, 
					bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
		for (m = 0; m < l2->numVerts; m++) 
		{
		    for (t = 0 ; t < tbind ; t ++)
			pfCopyVec2(texCoords[t][j], l2->texCoords[t][m]);

		    j ++;
		}
                l2 = l2->next;
            }
 	    for (t = 0 ; t < tbind ; t ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, 
				PFGS_PER_VERTEX, texCoords[t], NULL);
        }
        {
            coords = pfMalloc(sizeof(pfVec3) * numLineStripVerts, bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
		for (m = 0; m < l2->numVerts; m++)
                {
                    pfCopyVec3(coords[j++], l2->coords[m]);
		}
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, coords, NULL);
        }

        linestrips = l2;
        pfAdd(bldr->gsetList, gset);
    }
    else
    {
        gset = pfNewGSet(bldr->sharedArena);
	numLines = 1;
        numLineStrips = 1;
        numLineStripVerts = l2->numVerts;
        while ((totalLineStrips>1) && (l2->pixelsize==l2->next->pixelsize))
        {
            numLineStrips++;
            numLineStripVerts += l2->next->numVerts;
            l2 = l2->next;
            totalLineStrips--;
        }
	totalLineStrips--;

        while ((totalLines>1) && (l1->pixelsize==l1->next->pixelsize))
        {
            numLines++;
            l1 = l1->next;
            totalLines--;
        }
	totalLines--;

	m = numLineStrips + numLines;
        pfGSetPrimType(gset, PFGS_LINESTRIPS);
        pfGSetNumPrims(gset, m);

        lengthList = (int *) pfMalloc( sizeof(int) * m, bldr->sharedArena);
        pfGSetPrimLengths(gset, lengthList);
	for (k=0, l1=lines; k<numLines; l1=l1->next, k++) 
	{
	    lengthList[k] = 2;
	}
	for (l2=linestrips; k<m; l2=l2->next, k++)
	{
	    lengthList[k] = l2->numVerts;
	}
        pfGSetLineWidth(gset, linestrips->pixelsize);

        switch (cbind)
        {
        case PFGS_PER_VERTEX:
            colors = pfMalloc(sizeof(pfVec4) * (2*numLines + numLineStripVerts), bldr->sharedArena);
            j = 0;
	    l1 = lines;
            l2 = linestrips;
	    for (i = 0; i < numLines; i++) 
	    {
                pfCopyVec4(colors[j++], l1->colors[0]);
                pfCopyVec4(colors[j++], l1->colors[1]);
	
		l1 = l1->next;
	    }
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts; m++) {
                    pfCopyVec4(colors[j++], l2->colors[m]);
                }
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_VERTEX, colors, NULL);
            break;

        case PFGS_PER_PRIM:
            colors = pfMalloc(sizeof(pfVec4) * (numLines+numLineStrips), bldr->sharedArena);
	    j = 0;
	    l1 = lines;
            l2 = linestrips;

            for (i = 0; i < numLines; i++)
            {
                pfCopyVec4(colors[j++], l1->colors[0]);
		l1 = l1->next;
	    }
            for (i = 0; i < numLineStrips; i++)
            {
                pfCopyVec4(colors[j++], l2->colors[0]);
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_PRIM, colors, NULL);
            break;

        case PFGS_OVERALL:
            colors = pfMalloc(sizeof(pfVec4), bldr->sharedArena);
            pfCopyVec4(colors[0], linestrips->colors[0]);
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, colors, NULL);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_COLOR4, cbind, NULL, NULL);
            break;
        }

        switch (nbind)
        {
        case PFGS_PER_VERTEX:
            norms = pfMalloc(sizeof(pfVec3) * (2*numLines + numLineStripVerts), bldr->sharedArena);
            j = 0;
            l1 = lines;
            l2 = linestrips;
            for (i = 0; i < numLines; i++)
            {
                pfCopyVec3(norms[j++], l1->norms[0]);
                pfCopyVec3(norms[j++], l1->norms[1]);

                l1 = l1->next;
            }
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts; m++) {
                    pfCopyVec3(norms[j++], l2->norms[m]);
                }
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX, norms, NULL);
            break;

        case PFGS_PER_PRIM:
            norms = pfMalloc(sizeof(pfVec3) * (numLines+numLineStrips), bldr->sharedArena);
            j = 0;
            l1 = lines;
            l2 = linestrips;

            for (i = 0; i < numLines; i++)
            {
                pfCopyVec3(norms[j++], l1->norms[0]);
                l1 = l1->next;
            }
            for (i = 0; i < numLineStrips; i++)
            {
                pfCopyVec3(norms[j++], l2->norms[0]);
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_PRIM, norms, NULL);
            break;

        case PFGS_OVERALL:
            norms = pfMalloc(sizeof(pfVec3), bldr->sharedArena);
            pfCopyVec3(norms[0], linestrips->norms[0]);
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_OVERALL, norms, NULL);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_NORMAL3, nbind, NULL, NULL);
            break;
        }

        if (tbind != 0)
	{
	    for (t = 0 ; t < tbind ; t ++)
		texCoords[t] = pfMalloc(sizeof(pfVec2) * 
			(2*numLines+numLineStripVerts), bldr->sharedArena);
	    j = 0;
	    l1 = lines;
	    l2 = linestrips;
	    for (i = 0; i < numLines; i++)
	    {
		for (t = 0 ; t < tbind ; t ++)
		{
		    pfCopyVec2(texCoords[t][j  ], l1->texCoords[t][0]);
		    pfCopyVec4(texCoords[t][j+1], l1->texCoords[t][1]);
		}

		j += 2;
		l1 = l1->next;
	    }
	    for (i = 0; i < numLineStrips; i++)
	    {
		for (m = 0; m < l2->numVerts; m++) 
		{
		    for (t = 0 ; t < tbind ; t ++)
			pfCopyVec2(texCoords[t][j], l2->texCoords[t][m]);

		    j ++;
		}
		l2 = l2->next;
	    }

	    for (t = 0 ; t < tbind ; t ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, 
				PFGS_PER_VERTEX, texCoords[t], NULL);
	}

        {
	coords = pfMalloc(sizeof(pfVec3) * (2*numLines+numLineStripVerts), bldr->sharedArena);
	j = 0;
	l1 = lines;
	for (i = 0; i < numLines; i++)
	{
	    pfCopyVec3(coords[j++], l1->coords[0]);
	    pfCopyVec3(coords[j++], l1->coords[1]);

	    l1 = l1->next;
	}
	l2 = linestrips;
	for (i = 0; i < numLineStrips; i++)
	{
	    for (m = 0; m < l2->numVerts; m++)
	    {
		pfCopyVec3(coords[j++], l2->coords[m]);
	    }
	    l2 = l2->next;
	}
	pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, coords, NULL);
        }
	lines = l1;
	linestrips = l2;
        pfAdd(bldr->gsetList, gset);
    }
    }
}

/*
 * makeFlatLineGSets -- Make pfGeoSet(s) for non-indexed flat lines and linestrips
 */
static void
makeFlatLineGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind)
{
    pfGeoSet     *gset;
    int           i, j, k, m, t;
    int	    	  totalLineStrips;
    int	    	  numLineStrips, numLineStripVerts;
    int	   	 *lengthList;
    pfdGeom      *linestrips, *l2;

    pfVec3       *coords	= NULL;
    pfVec3       *norms		= NULL;
    pfVec4       *colors	= NULL;
    pfVec2       *texCoords[PF_MAX_TEXTURES];

    ushort	 *icoords	= NULL;
    ushort	 *inorms	= NULL;
    ushort	 *icolors	= NULL;
    ushort	 *itexCoords[PF_MAX_TEXTURES];

    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
    {
	texCoords[t] = NULL;
	itexCoords[t] = NULL;
    }
    
    linestrips      = bldr->flatlinestripBins  [nbind][cbind][tbind];
    totalLineStrips = bldr->flatlinestripCounts[nbind][cbind][tbind];

    while (totalLineStrips > 0) 
    {
        gset = pfNewGSet(bldr->sharedArena);
	l2 = linestrips;
        numLineStrips = 1;
        numLineStripVerts = l2->numVerts;
        while ((totalLineStrips>1) && (l2->pixelsize==l2->next->pixelsize))
        {
            numLineStrips++;
            numLineStripVerts += l2->next->numVerts;
            l2 = l2->next;
            totalLineStrips--;
        }
	totalLineStrips--;

        pfGSetPrimType(gset, PFGS_FLAT_LINESTRIPS);
        pfGSetNumPrims(gset, numLineStrips);

        lengthList = (int *) pfMalloc( sizeof(int) * numLineStrips, bldr->sharedArena);
        pfGSetPrimLengths(gset, lengthList);
        for (k=0, l2=linestrips; k<numLineStrips; k++, l2=l2->next)
        {
            lengthList[k] = l2->numVerts;
        }

        pfGSetLineWidth(gset, linestrips->pixelsize);

        switch (cbind)
        {
        case PFGS_PER_VERTEX:
            colors = pfMalloc(sizeof(pfVec4) * (numLineStripVerts-numLineStrips), bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts-1; m++) {
                    pfCopyVec4(colors[j++], l2->colors[m]);
                }
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_VERTEX, colors, NULL);
            break;

        case PFGS_PER_PRIM:
            colors = pfMalloc(sizeof(pfVec4) * numLineStrips, bldr->sharedArena);
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                pfCopyVec4(colors[i], l2->colors[0]);
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_PRIM, colors, NULL);
            break;

        case PFGS_OVERALL:
            colors = pfMalloc(sizeof(pfVec4), bldr->sharedArena);
            pfCopyVec4(colors[0], linestrips->colors[0]);
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, colors, NULL);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_COLOR4, cbind, NULL, NULL);
            break;
        }

        switch (nbind)
        {
        case PFGS_PER_VERTEX:
            norms = pfMalloc(sizeof(pfVec3) * (numLineStripVerts-numLineStrips), bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts-1; m++) {
                    pfCopyVec3(norms[j++], l2->norms[m]);
                }
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX, norms, NULL);
            break;

        case PFGS_PER_PRIM:
            norms = pfMalloc(sizeof(pfVec3) * numLineStrips, bldr->sharedArena);
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                pfCopyVec3(norms[i], l2->norms[0]);
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_PRIM, norms, NULL);
            break;

        case PFGS_OVERALL:
            norms = pfMalloc(sizeof(pfVec3), bldr->sharedArena);
            pfCopyVec3(norms[0], linestrips->norms[0]);
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_OVERALL, norms, NULL);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_NORMAL3, nbind, NULL, NULL);
            break;

        }
        if (tbind != 0)
        {
	    for (t = 0 ; t < tbind ; t ++)
		texCoords[t] = pfMalloc(sizeof(pfVec2) * numLineStripVerts, 
						bldr->sharedArena);            
	    j = 0;
	    l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts; m++)
                {
		    for (t = 0 ; t < tbind ; t ++)
			pfCopyVec2(texCoords[t][j], l2->texCoords[t][m]);

		    j ++;
                }
                l2 = l2->next;
            }
	    for (t = 0 ; t < tbind ; t ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, 
				PFGS_PER_VERTEX, texCoords[t], NULL);
        }
        {
            coords = pfMalloc(sizeof(pfVec3) * numLineStripVerts, bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts; m++)
                {
                    pfCopyVec3(coords[j++], l2->coords[m]);
                }
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, coords, NULL);
        }
        linestrips = l2;
        pfAdd(bldr->gsetList, gset);
    }
}

/*
 * makeIndexedLineGSets -- Make pfGeoSet(s) for indexed lines and linestrips
 */
static void
makeIndexedLineGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind)
{
    pfGeoSet     *gset;
    int           i, j, k, m, t;
    int	    	  totalLines, totalLineStrips;
    int	    	  numLines, numLineStrips, numLineStripVerts;
    int	   	 *lengthList;
    pfdPrim	 *lines, *l1;
    pfdGeom      *linestrips, *l2;
    int icoords_share;
    int ilist_size;

    pfVec3       *coords        = NULL;
    pfVec3       *norms         = NULL;
    pfVec4       *colors        = NULL;
    pfVec2       *texCoords[PF_MAX_TEXTURES];

    ushort       *icoords       = NULL;
    ushort       *inorms        = NULL;
    ushort       *icolors       = NULL;
    ushort       *itexCoords[PF_MAX_TEXTURES];

    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
    {
        texCoords[t] = NULL;
        itexCoords[t] = NULL;
    }

    
    lines           = bldr->indexedLineBins       [nbind][cbind][tbind];
    totalLines      = bldr->indexedLineCounts     [nbind][cbind][tbind];

    linestrips      = bldr->indexedlinestripBins  [nbind][cbind][tbind];
    totalLineStrips = bldr->indexedlinestripCounts[nbind][cbind][tbind];

    while ((totalLines!=0) || (totalLineStrips!=0)) 
    {
	l1 = lines;
	l2 = linestrips;
	if ((totalLines!=0)&&((totalLineStrips==0) ||
		(l1->pixelsize > l2->pixelsize)))
	{
	gset = pfNewGSet(bldr->sharedArena);
	numLines = 1;
	while ((totalLines>1) && (l1->pixelsize==l1->next->pixelsize))
	{
	    numLines++;
	    l1 = l1->next;
	    totalLines--;
	}
	totalLines--;

	pfGSetPrimType(gset, PFGS_LINES);
	pfGSetNumPrims(gset, numLines);
        pfGSetLineWidth(gset, lines->coords[2][0]);

	{
	    icoords = pfMalloc(sizeof(unsigned short) * 2 * numLines,
			       bldr->sharedArena);
	    j = 0;
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		icoords[j++] = l1->icoords[0];
		icoords[j++] = l1->icoords[1];

		l1 = l1->next;
	    }
	    pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, 
		       bldr->coordList, icoords);
	}

	switch (cbind)
	{
	case PFGS_PER_VERTEX:
	    ilist_size = sizeof(unsigned short) * 2 * numLines;
            icolors = pfMalloc(ilist_size, bldr->sharedArena);
	    j = 0;
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		icolors[j++] = l1->icolors[0];
		icolors[j++] = l1->icolors[1];

		l1 = l1->next;
	    }
	    if (bldr->shareIndexLists &&
		memcmp(icoords, icolors, ilist_size) == 0)
	    {
		pfFree(icolors);
		icolors = icoords;
	    }
	    pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_VERTEX,
		       bldr->colorList, icolors);
	    break;

	case PFGS_PER_PRIM:
	    icolors = pfMalloc(sizeof(unsigned short) * numLines, bldr->sharedArena);
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		icolors[i] = l1->icolors[0];

		l1 = l1->next;
	    }
	    pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_PRIM, bldr->colorList, icolors);
	    break;

	case PFGS_OVERALL:
	    colors = pfMalloc(sizeof(unsigned short), bldr->sharedArena);
	    icolors[0] = lines->icolors[0];
	    pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, bldr->colorList, icolors);
	    break;

	case PFGS_OFF:
	    pfGSetAttr(gset, PFGS_COLOR4, cbind, NULL, NULL);
	    break;
	}

	switch (nbind)
	{
	case PFGS_PER_VERTEX:
	    ilist_size = sizeof(unsigned short) * 2 * numLines;
            inorms = pfMalloc(ilist_size, bldr->sharedArena);
	    j = 0;
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		inorms[j++] = l1->inorms[0];
		inorms[j++] = l1->inorms[1];

		l1 = l1->next;
	    }
	    if (bldr->shareIndexLists &&
		memcmp(icoords, inorms, ilist_size) == 0)
	    {
		pfFree(inorms);
		inorms = icoords;
	    }
	    pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX, 
						bldr->normList, inorms);
	    break;

	case PFGS_PER_PRIM:
	    inorms = pfMalloc(sizeof(unsigned short) * numLines, bldr->sharedArena);
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		inorms[i] = l1->inorms[0];

		l1 = l1->next;
	    }
	    pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_PRIM, 
						bldr->normList, inorms);
	    break;

	case PFGS_OVERALL:
	    inorms = pfMalloc(sizeof(unsigned short), bldr->sharedArena);
	    inorms[0] = lines->inorms[0];
	    pfGSetAttr(gset, PFGS_NORMAL3, PFGS_OVERALL, 
						bldr->normList, inorms);
	    break;

	case PFGS_OFF:
	    pfGSetAttr(gset, PFGS_NORMAL3, nbind, NULL, NULL);
	    break;
	}

	if (tbind != 0)
	{
	    ilist_size = sizeof(unsigned short) * 2 * numLines;
	    for (t = 0 ; t < tbind ; t ++)
		itexCoords[t] = pfMalloc(ilist_size, bldr->sharedArena);
	    j = 0;
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		for (t = 0 ; t < tbind ; t ++)
		{
		    itexCoords[t][j  ] = l1->itexCoords[t][0];
		    itexCoords[t][j+1] = l1->itexCoords[t][1];
		}

		j += 2;
		l1 = l1->next;
	    }

	    for (t = 0 ; t < tbind ; t ++)
		if (bldr->shareIndexLists &&
		    memcmp(icoords, itexCoords[t], ilist_size) == 0)
		{
		    pfFree(itexCoords[t]);
		    itexCoords[t] = icoords;
		}

	    for (t = 0 ; t < tbind ; t ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, PFGS_PER_VERTEX,
					bldr->texCoordList[t], itexCoords[t]);
	}


	/* perform line strip generation */
	if (bldr->mesh)
	{
	    pfGeoSet *strip = NULL;
	    strip = pfdMeshGSet(gset);
	    if (strip != NULL)
	    {
		pfDelete(gset);
		gset = strip;
	    }
	}

	lines = l1;
        pfAdd(bldr->gsetList, gset);
    }
    else if ((totalLineStrips!=0)&&((totalLines==0) ||
	(l1->pixelsize < l2->pixelsize))) /* line strips */
    {
        gset = pfNewGSet(bldr->sharedArena);
        numLineStrips = 1;
	numLineStripVerts = l2->numVerts;
        while ((totalLineStrips>1) && (l2->pixelsize==l2->next->pixelsize))
        {
            numLineStrips++;
	    numLineStripVerts += l2->next->numVerts;
            l2 = l2->next;
	    totalLineStrips--;
        }
	totalLineStrips--;

        pfGSetPrimType(gset, PFGS_LINESTRIPS);
        pfGSetNumPrims(gset, numLineStrips);

	lengthList = (int *) pfMalloc( sizeof(int) * numLineStrips, bldr->sharedArena);
	pfGSetPrimLengths(gset, lengthList);
	for (k=0, l2=linestrips; k<numLineStrips; k++, l2=l2->next)
	{
	    lengthList[k] = l2->numVerts;
	}

	pfGSetLineWidth(gset, linestrips->pixelsize);

	{
	    icoords = pfMalloc(sizeof(unsigned short) * numLineStripVerts,
			       bldr->sharedArena);
	    j = 0;
	    l2 = linestrips;
	    for (i = 0; i < numLineStrips; i++)
	    {
		for (m = 0; m < l2->numVerts; m++)
		{
		    icoords[j++] = l2->icoords[m];
		}
		l2 = l2->next;
	    }
	    pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, 
		       bldr->coordList, icoords);
	}

        switch (cbind)
        {
        case PFGS_PER_VERTEX:
	    ilist_size = sizeof(unsigned short) * numLineStripVerts;
            icolors = pfMalloc(ilist_size, bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
		for (m = 0; m < l2->numVerts; m++) {
                    icolors[j++] = l2->icolors[m];
		}
                l2 = l2->next;
            }
	    if (bldr->shareIndexLists &&
		memcmp(icoords, icolors, ilist_size) == 0)
	    {
		pfFree(icolors);
		icolors = icoords;
	    }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_VERTEX, 
				bldr->colorList, icolors);
            break;

        case PFGS_PER_PRIM:
            icolors = pfMalloc(sizeof(unsigned short) * numLineStrips, bldr->sharedArena);
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                icolors[i] = l2->icolors[0];
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_PRIM, 
                                    bldr->colorList, icolors);
            break;

        case PFGS_OVERALL:
            icolors = pfMalloc(sizeof(unsigned short), bldr->sharedArena);
            icolors[0] = linestrips->icolors[0];
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, 
                                    bldr->colorList, icolors);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_COLOR4, cbind, NULL, NULL);
            break;
        }

        switch (nbind)
        {
        case PFGS_PER_VERTEX:
	    ilist_size = sizeof(unsigned short) * numLineStripVerts;
            inorms = pfMalloc(ilist_size, bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts; m++) {
                    inorms[j++] = l2->inorms[m];
		}
                l2 = l2->next;
            }
	    if (bldr->shareIndexLists &&
		memcmp(icoords, inorms, ilist_size) == 0)
	    {
		pfFree(inorms);
		inorms = icoords;
	    }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX, 
                                                bldr->normList, inorms);
            break;

        case PFGS_PER_PRIM:
            inorms = pfMalloc(sizeof(unsigned short) * numLineStrips, bldr->sharedArena);
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                inorms[i] = l2->inorms[0];
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_PRIM, 
                                                bldr->normList, inorms);
            break;

        case PFGS_OVERALL:
            inorms = pfMalloc(sizeof(unsigned short), bldr->sharedArena);
            inorms[0] = linestrips->inorms[0];
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_OVERALL, 
                                                bldr->normList, inorms);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_NORMAL3, nbind, NULL, NULL);
            break;
        }
        if (tbind != 0)
        {
	    ilist_size = sizeof(unsigned short) * numLineStripVerts;

	    for (t = 0 ; t < tbind ; t ++)
		itexCoords[t] = pfMalloc(ilist_size, bldr->sharedArena);

	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
		for (m = 0; m < l2->numVerts; m++) 
		{
		    for (t = 0 ; t < tbind ; t ++)
			itexCoords[t][j] = l2->itexCoords[t][m];

		    t ++;
		}
                l2 = l2->next;
            }

	    for (t = 0 ; t < tbind ; t ++)
		if (bldr->shareIndexLists &&
		    memcmp(icoords, itexCoords[t], ilist_size) == 0)
		{
		    pfFree(itexCoords[t]);
		    itexCoords[t] = icoords;
		}

	    for (t = 0 ; t < tbind ; t ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, PFGS_PER_VERTEX,  
                                    bldr->texCoordList[t], itexCoords[t]);
        }

        linestrips = l2;
        pfAdd(bldr->gsetList, gset);
    }
    else
    {
        gset = pfNewGSet(bldr->sharedArena);
	numLines = 1;
        numLineStrips = 1;
        numLineStripVerts = l2->numVerts;
        while ((totalLineStrips>1) && (l2->pixelsize==l2->next->pixelsize))
        {
            numLineStrips++;
            numLineStripVerts += l2->next->numVerts;
            l2 = l2->next;
            totalLineStrips--;
        }
	totalLineStrips--;
        while ((totalLines>1) && (l1->pixelsize==l1->next->pixelsize))
        {
            numLines++;
            l1 = l1->next;
            totalLines--;
        }
	totalLines--;

	m = numLineStrips + numLines;
        pfGSetPrimType(gset, PFGS_LINESTRIPS);
        pfGSetNumPrims(gset, m);

        lengthList = (int *) pfMalloc( sizeof(int) * m, bldr->sharedArena);
        pfGSetPrimLengths(gset, lengthList);
	for (k=0, l1=lines; k<numLines; l1=l1->next, k++) 
	{
	    lengthList[k] = 2;
	}
	for (l2=linestrips; k<m; l2=l2->next, k++)
	{
	    lengthList[k] = l2->numVerts;
	}
        pfGSetLineWidth(gset, linestrips->pixelsize);

	{
	    icoords = pfMalloc(sizeof(unsigned short) *
			       (2*numLines+numLineStrips), bldr->sharedArena);
	    j = 0;
	    l1 = lines;
	    for (i = 0; i < numLines; i++)
	    {
		icoords[j++] = l1->icoords[0];
		icoords[j++] = l1->icoords[1];

		l1 = l1->next;
	    }
	    l2 = linestrips;
	    for (i = 0; i < numLineStrips; i++)
	    {
		for (m = 0; m < l2->numVerts; m++)
		{
		    icoords[j++] = l2->icoords[m];
		}
		l2 = l2->next;
	    }
	    pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, 
		       bldr->coordList, icoords);
	}
	
        switch (cbind)
        {
        case PFGS_PER_VERTEX:
	    ilist_size = sizeof(unsigned short) *
			 (2*numLines + numLineStripVerts);
            icolors = pfMalloc(ilist_size, bldr->sharedArena);
            j = 0;
	    l1 = lines;
            l2 = linestrips;
	    for (i = 0; i < numLines; i++) 
	    {
                icolors[j++] = l1->icolors[0];
                icolors[j++] = l1->icolors[1];
	
		l1 = l1->next;
	    }
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts; m++) {
                    icolors[j++] = l2->icolors[m];
                }
                l2 = l2->next;
            }
	    if (bldr->shareIndexLists &&
		memcmp(icoords, icolors, ilist_size) == 0)
	    {
		pfFree(icolors);
		icolors = icoords;
	    }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_VERTEX, 
                                    bldr->colorList, icolors);
            break;

        case PFGS_PER_PRIM:
            icolors = pfMalloc(sizeof(unsigned short) * (numLines+numLineStrips), bldr->sharedArena);
	    j = 0;
	    l1 = lines;
            l2 = linestrips;

            for (i = 0; i < numLines; i++)
            {
                icolors[j++] = l1->icolors[0];
		l1 = l1->next;
	    }
            for (i = 0; i < numLineStrips; i++)
            {
                icolors[j++] = l2->icolors[0];
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_PRIM, 
                                    bldr->colorList, icolors);
            break;

        case PFGS_OVERALL:
            icolors = pfMalloc(sizeof(unsigned short), bldr->sharedArena);
            icolors[0] = linestrips->icolors[0];
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, 
                                    bldr->colorList, icolors);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_COLOR4, cbind, NULL, NULL);
            break;
        }

        switch (nbind)
        {
        case PFGS_PER_VERTEX:
	    ilist_size = sizeof(unsigned short) *
			 (2*numLines + numLineStripVerts);
            inorms = pfMalloc(ilist_size, bldr->sharedArena);
            j = 0;
            l1 = lines;
            l2 = linestrips;
            for (i = 0; i < numLines; i++)
            {
                inorms[j++] = l1->inorms[0];
                inorms[j++] = l1->inorms[1];

                l1 = l1->next;
            }
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts; m++) {
                    inorms[j++] = l2->inorms[m];
                }
                l2 = l2->next;
            }
	    if (bldr->shareIndexLists &&
		memcmp(icoords, inorms, ilist_size) == 0)
	    {
		pfFree(inorms);
		inorms = icoords;
	    }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX, 
                                                bldr->normList, inorms);
            break;

        case PFGS_PER_PRIM:
            inorms = pfMalloc(sizeof(unsigned short) * (numLines+numLineStrips), bldr->sharedArena);
            j = 0;
            l1 = lines;
            l2 = linestrips;

            for (i = 0; i < numLines; i++)
            {
                inorms[j++] = l1->inorms[0];
                l1 = l1->next;
            }
            for (i = 0; i < numLineStrips; i++)
            {
                inorms[j++] = l2->inorms[0];
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_PRIM, 
                                                bldr->normList, inorms);
            break;

        case PFGS_OVERALL:
            inorms = pfMalloc(sizeof(unsigned short), bldr->sharedArena);
            inorms[0] = linestrips->inorms[0];
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_OVERALL, 
                                                bldr->normList, inorms);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_NORMAL3, nbind, NULL, NULL);
            break;
        }

        if (tbind != 0)
        {
	    ilist_size = sizeof(unsigned short) *
			 (2*numLines + numLineStripVerts);

	    for (t = 0 ; t < tbind ; t ++)
		itexCoords[t] = pfMalloc(ilist_size, bldr->sharedArena);

            j = 0;
            l1 = lines;
            l2 = linestrips;
            for (i = 0; i < numLines; i++)
            {
		for (t = 0 ; t < tbind ; t ++)
		{
		    itexCoords[t][j  ] = l1->itexCoords[t][0];
		    itexCoords[t][j+1] = l1->itexCoords[t][1];
		}

		j += 2;
                l1 = l1->next;
            }
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts; m++) 
		{
		    for (t = 0 ; t < tbind ; t ++)
			itexCoords[t][j] = l2->itexCoords[t][m];
                }
		j ++;
                l2 = l2->next;
            }
	    for (t = 0 ; t < tbind ; t ++)
		if (bldr->shareIndexLists &&
		    memcmp(icoords, itexCoords[t], ilist_size) == 0)
		{
		    pfFree(itexCoords[t]);
		    itexCoords[t] = icoords;
		}

	    for (t = 0 ; t < tbind ; t ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, PFGS_PER_VERTEX, 
                                    bldr->texCoordList[t], itexCoords[t]);
	}

	lines = l1;
	linestrips = l2;
        pfAdd(bldr->gsetList, gset);
    }
    }
}

/*
 * makeIndexedFlatLineGSets -- Make pfGeoSet(s) for indexed flat lines and linestrips
 */
static void
makeIndexedFlatLineGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind)
{
    pfGeoSet     *gset;
    int           i, j, k, m, t;
    int	    	  totalLineStrips;
    int	    	  numLineStrips, numLineStripVerts;
    int	   	 *lengthList;
    pfdGeom      *linestrips, *l2;

    pfVec3       *coords        = NULL;
    pfVec3       *norms         = NULL;
    pfVec4       *colors        = NULL;
    pfVec2       *texCoords[PF_MAX_TEXTURES];

    ushort       *icoords       = NULL;
    ushort       *inorms        = NULL;
    ushort       *icolors       = NULL;
    ushort       *itexCoords[PF_MAX_TEXTURES];

    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
    {
        texCoords[t] = NULL;
        itexCoords[t] = NULL;
    }

    
    linestrips      = bldr->indexedflatlinestripBins  [nbind][cbind][tbind];
    totalLineStrips = bldr->indexedflatlinestripCounts[nbind][cbind][tbind];

    while (totalLineStrips > 0) 
    {
	l2 = linestrips;
        gset = pfNewGSet(bldr->sharedArena);
        numLineStrips = 1;
        numLineStripVerts = l2->numVerts;
        while ((totalLineStrips>1) && (l2->pixelsize==l2->next->pixelsize))
        {
            numLineStrips++;
            numLineStripVerts += l2->next->numVerts;
            l2 = l2->next;
            totalLineStrips--;
        }
	totalLineStrips--;

        pfGSetPrimType(gset, PFGS_FLAT_LINESTRIPS);
        pfGSetNumPrims(gset, numLineStrips);

        lengthList = (int *) pfMalloc( sizeof(int) * numLineStrips, bldr->sharedArena);
        pfGSetPrimLengths(gset, lengthList);
        for (k=0, l2=linestrips; k<numLineStrips; k++, l2=l2->next)
        {
            lengthList[k] = l2->numVerts;
        }

        pfGSetLineWidth(gset, linestrips->pixelsize);

        switch (cbind)
        {
        case PFGS_PER_VERTEX:
            icolors = pfMalloc(sizeof(unsigned short) * (numLineStripVerts-numLineStrips), bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts-1; m++) {
                    icolors[j++] = l2->icolors[m];
                }
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_VERTEX, 
                                    bldr->colorList, icolors);
            break;

        case PFGS_PER_PRIM:
            icolors = pfMalloc(sizeof(unsigned short) * numLineStrips, bldr->sharedArena);
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                icolors[i] = l2->icolors[0];
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_PRIM, 
                                    bldr->colorList, icolors);
            break;

        case PFGS_OVERALL:
            icolors = pfMalloc(sizeof(unsigned short), bldr->sharedArena);
            icolors[0] = linestrips->icolors[0];
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, 
                                    bldr->colorList, icolors);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_COLOR4, cbind, NULL, NULL);
            break;
        }

        switch (nbind)
        {
        case PFGS_PER_VERTEX:
            inorms = pfMalloc(sizeof(unsigned short) * (numLineStripVerts-numLineStrips), bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts-1; m++) {
                    inorms[j++] = l2->inorms[m];
                }
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX, 
                                                bldr->normList, inorms);
            break;

        case PFGS_PER_PRIM:
            inorms = pfMalloc(sizeof(unsigned short) * numLineStrips, bldr->sharedArena);
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                inorms[i] = l2->inorms[0];
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_PRIM, 
                                                bldr->normList, inorms);
            break;

        case PFGS_OVERALL:
            inorms = pfMalloc(sizeof(unsigned short), bldr->sharedArena);
            inorms[0] = linestrips->inorms[0];
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_OVERALL, 
                                                bldr->normList, inorms);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_NORMAL3, nbind, NULL, NULL);
            break;

        }
        if (tbind != 0)
        {
	    for (t = 0 ; t < tbind ; t ++)
		itexCoords[t] = pfMalloc(sizeof(unsigned short) * 
					numLineStripVerts, bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts; m++)
                {
		    for (t = 0 ; t < tbind ; t ++)
			itexCoords[t][j] = l2->itexCoords[t][m];

		    j ++;
                }
                l2 = l2->next;
            }

	    for (t = 0 ; t < tbind ; t ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, PFGS_PER_VERTEX, 
                                    bldr->texCoordList[t], itexCoords[t]);
        }
        {
            icoords = pfMalloc(sizeof(unsigned short) * numLineStripVerts, bldr->sharedArena);
	    j = 0;
            l2 = linestrips;
            for (i = 0; i < numLineStrips; i++)
            {
                for (m = 0; m < l2->numVerts; m++)
                {
                    icoords[j++] = l2->icoords[m];
                }
                l2 = l2->next;
            }
            pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, 
                                        bldr->coordList, icoords);
        }

        linestrips = l2;
        pfAdd(bldr->gsetList, gset);
    }
}

/*
 * makePointGSets -- Make pfGeoSet(s) for non-indexed points
 */
static void
makePointGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind)
{
    pfGeoSet     *gset;
    int           i, t;
    int	    	  numPoints, totalPoints;
    pfdPrim	 *p, *points;

    pfVec3       *coords        = NULL;
    pfVec3       *norms         = NULL;
    pfVec4       *colors        = NULL;
    pfVec2       *texCoords[PF_MAX_TEXTURES];

    ushort       *icoords       = NULL;
    ushort       *inorms        = NULL;
    ushort       *icolors       = NULL;
    ushort       *itexCoords[PF_MAX_TEXTURES];

    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
    {
        texCoords[t] = NULL;
        itexCoords[t] = NULL;
    }
    
    points      = bldr->pointBins  [nbind][cbind][tbind];
    totalPoints = bldr->pointCounts[nbind][cbind][tbind];

    while (totalPoints > 0) 
    {
        gset = pfNewGSet(bldr->sharedArena);
	p = points;
        numPoints = 1;
        while ((totalPoints>1) && (p->pixelsize==p->next->pixelsize))
        {
            numPoints++;
            p = p->next;
            totalPoints--;
        }
	totalPoints--;

        pfGSetPrimType(gset, PFGS_POINTS);
        pfGSetNumPrims(gset, numPoints);

	pfGSetPntSize(gset, points->pixelsize);

        switch (cbind)
        {
        case PFGS_PER_VERTEX:
        case PFGS_PER_PRIM:
            colors = pfMalloc(sizeof(pfVec4) * numPoints, bldr->sharedArena);
	    p = points;
            for (i = 0; i < numPoints; i++)
            {
                pfCopyVec4(colors[i], p->colors[0]);
                p = p->next;
            }
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_VERTEX, colors, NULL);
            break;

        case PFGS_OVERALL:
            colors = pfMalloc(sizeof(pfVec4), bldr->sharedArena);
            pfCopyVec4(colors[0], points->colors[0]);
            pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, colors, NULL);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_COLOR4, cbind, NULL, NULL);
            break;
        }

        switch (nbind)
        {
        case PFGS_PER_VERTEX:
	case PFGS_PER_PRIM:
            norms = pfMalloc(sizeof(pfVec3) * numPoints, bldr->sharedArena);
            p = points; 
            for (i = 0; i < numPoints; i++)
            {
                pfCopyVec3(norms[i], p->norms[0]);
                p = p->next;
            }
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX, norms, NULL);
            break;

        case PFGS_OVERALL:
            norms = pfMalloc(sizeof(pfVec3), bldr->sharedArena);
            pfCopyVec3(norms[0], points->norms[0]);
            pfGSetAttr(gset, PFGS_NORMAL3, PFGS_OVERALL, norms, NULL);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_NORMAL3, nbind, NULL, NULL);
            break;

        }
        if (tbind != 0)
        {
	    for (t = 0 ; t < tbind ; t ++)
		texCoords[t] = pfMalloc(sizeof(pfVec2) * numPoints, 
					bldr->sharedArena);
	    p = points;
            for (i = 0; i < numPoints; i++)
            {
		for (t = 0 ; t < tbind ; t ++)
		    pfCopyVec2(texCoords[t][i], p->texCoords[t][0]);

                p = p->next;
            }

	    for (t = 0 ; t < tbind ; t ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, PFGS_PER_VERTEX, 
					texCoords[t], NULL);
        }
        {
            coords = pfMalloc(sizeof(pfVec3) * numPoints, bldr->sharedArena);
            p = points;
            for (i = 0; i < numPoints; i++)
            {
                pfCopyVec3(coords[i], p->coords[0]);
                p = p->next;
            }
            pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, coords, NULL);
        }

	points = p;
        pfAdd(bldr->gsetList, gset);
    }
}

/*
 * makeIndexedPointGSets -- Make pfGeoSet(s) for indexed points
 */
static void
makeIndexedPointGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind)
{
    pfGeoSet     *gset;
    int           i, t;
    int	    	  numPoints, totalPoints;
    pfdPrim	 *p, *points;
    int icoords_share;

    pfVec3       *norms         = NULL;
    pfVec4       *colors        = NULL;
    pfVec2       *texCoords[PF_MAX_TEXTURES];

    ushort       *icoords       = NULL;
    ushort       *inorms        = NULL;
    ushort       *icolors       = NULL;
    ushort       *itexCoords[PF_MAX_TEXTURES];

    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
    {
        texCoords[t] = NULL;
        itexCoords[t] = NULL;
    }

    
    points      = bldr->indexedPointBins  [nbind][cbind][tbind];
    totalPoints = bldr->indexedPointCounts[nbind][cbind][tbind];

    while (totalPoints > 0)
    {
        gset = pfNewGSet(bldr->sharedArena);
        p = points;
        numPoints = 1;
        while ((totalPoints>1) && (p->pixelsize==p->next->pixelsize))
        {
            numPoints++;
            p = p->next;
            totalPoints--;
        }
	totalPoints--;

        pfGSetPrimType(gset, PFGS_POINTS);
        pfGSetNumPrims(gset, numPoints);

        pfGSetPntSize(gset, points->pixelsize);

        {
	    icoords = pfMalloc(sizeof(unsigned short) * numPoints,
			       bldr->sharedArena);
	    p = points;
	    for (i = 0; i < numPoints; i++)
	    {
		icoords[i] = p->icoords[0];
		p = p->next;
	    }
	    pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, bldr->coordList,
		       icoords);
    	}

        switch (cbind)
	{
	case PFGS_PER_VERTEX:
	case PFGS_PER_PRIM:
	    p = points;
	    icoords_share = bldr->shareIndexLists;
	    for (i = 0; icoords_share && i < numPoints; i++)
	    {
		if (icoords[i] != p->icolors[0])
		    icoords_share = FALSE;
		p = p->next;
	    }
	    if (icoords_share)
		icolors = icoords;
	    else
	    {
		icolors = pfMalloc(sizeof(unsigned short) * numPoints,
				   bldr->sharedArena);
		p = points;
		for (i = 0; i < numPoints; i++)
		{
		    icolors[i] = p->icolors[0];
		    p = p->next;
		}
	    }
	    pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_VERTEX, bldr->colorList,
		       icolors);
	    break;

	case PFGS_OVERALL:
            icolors = pfMalloc(sizeof(unsigned short), bldr->sharedArena);
            icolors[0] = points->icolors[0];
	    pfGSetAttr(gset, PFGS_COLOR4, PFGS_OVERALL, bldr->colorList,
		       icolors);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_COLOR4, cbind, NULL, NULL);
            break;
        }

        switch (nbind)
        {
        case PFGS_PER_VERTEX:
        case PFGS_PER_PRIM:
	    p = points;
	    icoords_share = bldr->shareIndexLists;
	    for (i = 0; icoords_share && i < numPoints; i++)
	    {
		if (icoords[i] != p->inorms[0])
		    icoords_share = FALSE;
		p = p->next;
	    }
	    if (icoords_share)
		inorms = icoords;
	    else
	    {
		inorms = pfMalloc(sizeof(unsigned short) * numPoints,
				  bldr->sharedArena);
		p = points;
		for (i = 0; i < numPoints; i++)
		{
		    inorms[i] = p->inorms[0];
		    p = p->next;
		}
	    }
	    pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX, bldr->normList,
		       inorms);
	    break;

        case PFGS_OVERALL:
            inorms = pfMalloc(sizeof(unsigned short), bldr->sharedArena);
            inorms[0] = points->inorms[0];
	    pfGSetAttr(gset, PFGS_NORMAL3, PFGS_OVERALL, bldr->normList,
		       inorms);
            break;

        case PFGS_OFF:
            pfGSetAttr(gset, PFGS_NORMAL3, nbind, NULL, NULL);
            break;

        }

        if (tbind != 0)
        {
	    p = points;
	    icoords_share = bldr->shareIndexLists;
	    for (i = 0; icoords_share && i < numPoints; i++)
	    {
		for (t = 0 ; t < tbind ; t ++)
		    if (icoords[i] != p->itexCoords[t][0])
			icoords_share = FALSE;
		p = p->next;
	    }
	    if (icoords_share)
	    {
		for (t = 0 ; t < tbind ; t ++)
		    itexCoords[t] = icoords;
	    }
	    else
	    {
		for (t = 0 ; t < tbind ; t ++)
		    itexCoords[t] = pfMalloc(sizeof(unsigned short) * numPoints,
				      bldr->sharedArena);
		p = points;
		for (i = 0; i < numPoints; i++)
		{
		    for (t = 0 ; t < tbind ; t ++)
			itexCoords[t][i] = p->itexCoords[t][0];

		    p = p->next;
		}
	    }
	    for (t = 0 ; t < tbind ; t ++)
		pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, PFGS_PER_VERTEX,
		       bldr->texCoordList[t], itexCoords[t]);
        }

        points = p;
    	pfAdd(bldr->gsetList, gset);
    }
}

/*
 * makeGSets -- Make pfGeoSet(s) from primitive bins
 */
static void
makeGSets(pfdGeoBuilder *bldr, int nbind, int cbind, int tbind)
{
    /* points */
    makePointGSets(bldr, nbind, cbind, tbind);

    /* indexed points */
    makeIndexedPointGSets(bldr, nbind, cbind, tbind);

    /* lines and line strips */
    makeLineGSets(bldr, nbind, cbind, tbind);

    /*indexed lines, indexed line strips */
    makeIndexedLineGSets(bldr, nbind, cbind, tbind);

    /* [flat lines ??? and] flat line strips */
    makeFlatLineGSets(bldr, nbind, cbind, tbind);

    /* indexed flat lines and indexed flat line strips */
    makeIndexedFlatLineGSets(bldr, nbind, cbind, tbind);

    /* triangles */
    makeTriGSets(bldr, nbind, cbind, tbind);

    /* indexedtriangles */
    makeIndexedTriGSets(bldr, nbind, cbind, tbind);
}

/* ###################################################################### */
/* #                                                                    # */
/* #                   Concave polygon decomposition                    # */
/* #                                                                    # */
/* ###################################################################### */

typedef struct _Vtx
{
    int            index;
    pfVec3          coord;
    struct _Vtx    *next;
} Vtx;

#define VX(p, c)    p->coord[c]

/*
 * Decompose concave polygon into triangles. Return number of triangles.
 */
static int
decompConcave(pfdGeom *pgon, pfdPrim *triList, int asum, int x, int y)
{
    Vtx		*p0, *p1, *p2, *t0, *vert;
    float	xmin, xmax, ymin, ymax;
    int		i, init, csum, chek;
    long	m[3];
    float 	a[3], b[3], c[3], s[3];
    int         numPolys = 0, numTris = 0;
    Vtx		**vList;
    pfdPrim	*tri = triList;

    /* allocate vList */
    vList = (Vtx **)alloca(pgon->numVerts*sizeof(Vtx*));
    for (i = 0; i < pgon->numVerts; i++)
	vList[i] = NULL;

    /* Make linked list of verts */
    vert = (Vtx *) alloca(sizeof(Vtx));
    vert->index = 0;
    if (pgon->flags & PFD_INDEXED)
	pfCopyVec3(vert->coord, pgon->coordList[pgon->icoords[0]]);
    else
	pfCopyVec3(vert->coord, pgon->coords[0]);
    p1 = vert;
    vList[0] = vert;

    for (i = 1; i < pgon->numVerts; i++)
    {
	p0 = (Vtx *) alloca(sizeof(Vtx));
	vList[i] = p0;
	p0->index = i;
	if (pgon->flags & PFD_INDEXED)
	    pfCopyVec3(p0->coord, pgon->coordList[pgon->icoords[i]]);
	else
	    pfCopyVec3(p0->coord, pgon->coords[i]);
	p1->next = p0;
	p1 = p0;
    }
    p1->next = vert;

    p0 = vert;
    p1 = p0->next;
    p2 = p1->next;
    m[0] = (long) p0;
    m[1] = (long) p1;
    m[2] = (long) p2;
    chek = 0;

    while (p0 != p2->next)
    {
	/* Polygon is self-intersecting so punt */
	if (chek && m[0] == (long) p0 && 
		    m[1] == (long) p1 && 
		    m[2] == (long) p2)
	{
#ifdef	CHASE
	    pfdPrim  *t;
#endif

	    if (tri->next != NULL)
		pfNotify(PFNFY_WARN, PFNFY_PRINT, "decompConcave() "
		    "Could not decompose concave polygon!");

#ifdef	CHASE
	    t = triList;
	    while (t != NULL && t->next != tri)
		t = t->next;
	    if (t != NULL)
		t->next = NULL;
#endif
    	    return numTris;
	}

	chek = 1;

	a[0] = VX(p1, y) - VX(p2, y);
	b[0] = VX(p2, x) - VX(p1, x);
	a[2] = VX(p0, y) - VX(p1, y);
	b[2] = VX(p1, x) - VX(p0, x);

	csum = ((b[0] * a[2] - b[2] * a[0] >= 0.0) ? 1 : 0);

	/* current angle is concave */
	if (csum ^ asum)
	{
	    p0 = p1;
	    p1 = p2;
	    p2 = p2->next;
	}

	/* current angle is convex */
	else
	{
	    xmin = (VX(p0, x) < VX(p1, x)) ? VX(p0, x) : VX(p1, x);
	    if (xmin > VX(p2, x))
		xmin = VX(p2, x);

	    xmax = (VX(p0, x) > VX(p1, x)) ? VX(p0, x) : VX(p1, x);
	    if (xmax < VX(p2, x))
		xmax = VX(p2, x);

	    ymin = (VX(p0, y) < VX(p1, y)) ? VX(p0, y) : VX(p1, y);
	    if (ymin > VX(p2, y))
		ymin = VX(p2, y);

	    ymax = (VX(p0, y) > VX(p1, y)) ? VX(p0, y) : VX(p1, y);
	    if (ymax < VX(p2, y))
		ymax = VX(p2, y);

	    for (init = 1, t0 = p2->next; t0 != p0; t0 = t0->next)
		if (VX(t0, x) >= xmin && VX(t0, x) <= xmax &&
		    VX(t0, y) >= ymin && VX(t0, y) <= ymax)
		{
		    if (init)
		    {
			a[1] = VX(p2, y) - VX(p0, y);
			b[1] = VX(p0, x) - VX(p2, x);
			init = 0;
			c[0] = VX(p1, x) * VX(p2, y) - VX(p2, x) * VX(p1, y);
			c[1] = VX(p2, x) * VX(p0, y) - VX(p0, x) * VX(p2, y);
			c[2] = VX(p0, x) * VX(p1, y) - VX(p1, x) * VX(p0, y);
		    }

		    s[0] = a[0] * VX(t0, x) + b[0] * VX(t0, y) + c[0];
		    s[1] = a[1] * VX(t0, x) + b[1] * VX(t0, y) + c[1];
		    s[2] = a[2] * VX(t0, x) + b[2] * VX(t0, y) + c[2];

		    if (asum)
		    {
			if (s[0] >= 0.0 && s[1] >= 0.0 && s[2] >= 0.0)
			    break;
		    }
		    else
			if (s[0] <= 0.0 && s[1] <= 0.0 && s[2] <= 0.0)
			    break;
		}

	    if (t0 != p0)
	    {
		p0 = p1;
		p1 = p2;
		p2 = p2->next;
	    }
	    else
	    {
		copyGeom(tri, pgon, p0->index, p1->index, p2->index); 
		numTris++;

		p0->next = p1->next;
		p1 = p2;
		p2 = p2->next;

	        m[0] = (long) p0;
	        m[1] = (long) p1;
	        m[2] = (long) p2;
	        chek = 0;
		
		tri = tri->next;
	    }
	}
    }

    copyGeom(tri, pgon, p0->index, p1->index, p2->index); 
    numTris++;

    return numTris;
}

/* 
 * Triangulate the polygon pgon, given a linked list of new triangles.
 * This also decomposes concave polygons. Return TRUE if pgon is concave.
 */
static int
triangulatePoly(pfdGeom *pgon, pfdPrim *triList)
{
    register float  *p0, *p1;
    register float  dx1, dy1, dx2, dy2, max;
    register int   i, flag, asum, csum, index, x, y, v0, v1, v, even;
    float	    as[3];
    pfdPrim	    *tri;

    /* First see if polygon is just a triangle */
    if (pgon->numVerts == 3)
    {
	copyGeom(triList, pgon, 0, 1, 2);
	return 1;
    }

    /* calculate signed areas */
    as[0] = as[1] = as[2] = 0.0;

    for (i = 0; i < pgon->numVerts; i++)
    {
	if (pgon->flags & PFD_INDEXED)
	{
	    p0 = pgon->coordList[pgon->icoords[i]];
	    p1 = pgon->coordList[pgon->icoords[(i + 1) % pgon->numVerts]];
	}
	else
	{
	    p0 = pgon->coords[i];
	    p1 = pgon->coords[(i + 1) % pgon->numVerts];
	}
	as[0] += p0[0] * p1[1] - p0[1] * p1[0];
	as[1] += p0[0] * p1[2] - p0[2] * p1[0];
	as[2] += p0[1] * p1[2] - p0[2] * p1[1];
    }

    /* select largest signed area */
    for (max = 0.0, index = 0, i = 0; i < 3; i++)
	if (as[i] >= 0.0)
	{
	    if (as[i] > max)
	    {
		max = as[i];
		index = i;
		flag = 1;
	    }
	}
	else
	{
	    as[i] = -as[i];
	    if (as[i] > max)
	    {
		max = as[i];
		index = i;
		flag = 0;
	    }
	}

    /* pointer offsets */
    switch (index)
    {
    case 0:
	x = 0;
	y = 1;
	break;

    case 1:
	x = 0;
	y = 2;
	break;

    case 2:
	x = 1;
	y = 2;
	break;
    }

    /* concave check */
    if (pgon->flags & PFD_INDEXED)
    {
	p0 = pgon->coordList[pgon->icoords[0]];
	p1 = pgon->coordList[pgon->icoords[1]];
    }
    else
    {
	p0 = pgon->coords[0];
	p1 = pgon->coords[1];
    }
    dx1 = p1[x] - p0[x];
    dy1 = p1[y] - p0[y];
    p0 = p1;
   if (pgon->flags & PFD_INDEXED)
	p1 = pgon->coordList[pgon->icoords[2]];
    else
	p1 = pgon->coords[2];
    dx2 = p1[x] - p0[x];
    dy2 = p1[y] - p0[y];
    asum = ((dx1 * dy2 - dx2 * dy1 >= 0.0) ? 1 : 0);

    for (i = 0; i < pgon->numVerts - 1; i++)
    {
	p0 = p1;

	if (pgon->flags & PFD_INDEXED)
	    p1 = pgon->coordList[pgon->icoords[(i + 3) % pgon->numVerts]];
	else
	    p1 = pgon->coords[(i + 3) % pgon->numVerts];

	dx1 = dx2;
	dy1 = dy2;
	dx2 = p1[x] - p0[x];
	dy2 = p1[y] - p0[y];
	csum = ((dx1 * dy2 - dx2 * dy1 >= 0.0) ? 1 : 0);

	if (csum ^ asum)
	{
	    pgon->flags |= PFD_CONCAVE;
	    return decompConcave(pgon, triList, flag, x, y);
	}
    }

    v0 = 0;
    v1 = 1;
    v = pgon->numVerts - 1;
  
    even = 1;

    tri = triList;

    /* 
     * Convert to triangles only. Do not fan out from a single vertex
     * but zigzag into triangle strip.
     */
    for (i = 0; i < pgon->numVerts - 2; i++)
    {
	if (even)
	{
	    copyGeom(tri, pgon, v0, v1, v);

	    v0 = v1;
	    v1 = v;
	    v = v0 + 1;
	}
	else
	{
	    copyGeom(tri, pgon, v1, v0, v);

	    v0 = v1;
	    v1 = v;
	    v = v0 - 1;
	}

	even = !even;
	tri = tri->next;
    }

    return pgon->numVerts - 2;
}

/* 
 * Triangulate the polygon pgon, given a linked list of new triangles.
 * This also decomposes concave polygons. Return TRUE if pgon is concave.
 */
extern int
pfdTriangulatePoly(pfdGeom *pgon, pfdPrim *triList)
{
    return triangulatePoly(pgon, triList);    
}

/* ###################################################################### */
/* #                                                                    # */
/* #               what is this doing in the pfdGeoBuilder?             # */
/* #                                                                    # */
/* ###################################################################### */

void
pfdPrintGSet(pfGeoSet *gset)
{
    int i, j, k;
    int num, PrimTypes;
    int *lengths;
    pfVec3 *Coords;
    pfVec4 *Colors;
    pfVec2 *texCoords;
    unsigned short *tindex;
    unsigned short *cindex;
    unsigned short *vindex;

    num = pfGetGSetNumPrims(gset);
    pfNotify(PFNFY_ALWAYS, PFNFY_PRINT, "pfdOutputGSet");
    pfNotify(PFNFY_ALWAYS, PFNFY_PRINT, "  numPrims %d", 
	pfGetGSetNumPrims(gset));
    PrimTypes = pfGetGSetPrimType(gset);
    pfNotify(PFNFY_ALWAYS, PFNFY_PRINT, "  primTypes %d", 
	pfGetGSetPrimType(gset));
    pfNotify(PFNFY_ALWAYS, PFNFY_PRINT, "  linewidth %f", 
	pfGetGSetLineWidth(gset));

    pfGetGSetAttrLists(gset, PFGS_COLOR4, (void**)&Colors, &cindex);
    pfNotify(PFNFY_ALWAYS, PFNFY_PRINT,
        "color %f %f %f %f", Colors[0][0], Colors[0][1], Colors[0][2], Colors[0][3]);
    pfNotify(PFNFY_ALWAYS, PFNFY_PRINT,
	"color bind %d", pfGetGSetAttrBind(gset, PFGS_COLOR4));
    pfGetGSetAttrLists(gset, PFGS_TEXCOORD2, (void**)&texCoords, &tindex);
    pfNotify(PFNFY_ALWAYS, PFNFY_PRINT,
        "tex bind %d", pfGetGSetAttrBind(gset, PFGS_TEXCOORD2));
/*
    pfNotify(PFNFY_ALWAYS, PFNFY_PRINT, "texCoords %f %f %f %f %f %f %f %f", 
	texCoords[0][0], texCoords[0][1],
 	texCoords[1][0], texCoords[1][1], 
	texCoords[2][0], texCoords[2][1],
	texCoords[3][0], texCoords[3][1]);
*/

    k = 0;
    if ((PrimTypes == PFGS_LINESTRIPS) ||
	(PrimTypes == PFGS_FLAT_LINESTRIPS))
    {
	lengths = pfGetGSetPrimLengths(gset);
	for (i = 0; i < num; i++) 
	{
	    pfGetGSetAttrLists(gset, PFGS_COORD3, (void**)&Coords, &vindex);
	    for (j = 0; j < lengths[i]; j++)
	    {
		pfNotify(PFNFY_ALWAYS, PFNFY_PRINT, 
		    "  vertex %f %f %f", 
		    Coords[k][0], Coords[k][1], Coords[k][2]);
		k++;
	    }
	    pfNotify(PFNFY_ALWAYS, PFNFY_PRINT, "  length %d", lengths[i]);
	}
    }
}