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

File: [Development] / performer / src / lib / libpfutil / hash.c (download)

Revision 1.1, Tue Nov 21 21:39:36 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

/*
 * Copyright 1993, 1994, 1995, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 *
 * This source code ("Source Code") was originally derived from a
 * code base owned by Silicon Graphics, Inc. ("SGI")
 * 
 * LICENSE: SGI grants the user ("Licensee") permission to reproduce,
 * distribute, and create derivative works from this Source Code,
 * provided that: (1) the user reproduces this entire notice within
 * both source and binary format redistributions and any accompanying
 * materials such as documentation in printed or electronic format;
 * (2) the Source Code is not to be used, or ported or modified for
 * use, except in conjunction with OpenGL Performer; and (3) the
 * names of Silicon Graphics, Inc.  and SGI may not be used in any
 * advertising or publicity relating to the Source Code without the
 * prior written permission of SGI.  No further license or permission
 * may be inferred or deemed or construed to exist with regard to the
 * Source Code or the code base of which it forms a part. All rights
 * not expressly granted are reserved.
 * 
 * This Source Code is provided to Licensee AS IS, without any
 * warranty of any kind, either express, implied, or statutory,
 * including, but not limited to, any warranty that the Source Code
 * will conform to specifications, any implied warranties of
 * merchantability, fitness for a particular purpose, and freedom
 * from infringement, and any warranty that the documentation will
 * conform to the program, or any warranty that the Source Code will
 * be error free.
 * 
 * IN NO EVENT WILL SGI BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT
 * LIMITED TO DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES,
 * ARISING OUT OF, RESULTING FROM, OR IN ANY WAY CONNECTED WITH THE
 * SOURCE CODE, WHETHER OR NOT BASED UPON WARRANTY, CONTRACT, TORT OR
 * OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM,
 * OR AROSE OUT OF USE OR RESULTS FROM USE OF, OR LACK OF ABILITY TO
 * USE, THE SOURCE CODE.
 * 
 * Contact information:  Silicon Graphics, Inc., 
 * 1600 Amphitheatre Pkwy, Mountain View, CA  94043, 
 * or:  http://www.sgi.com
 */

/*
 * file: hash.c 
 * ------------
 * hash-table management
 *
 * $Revision: 1.1 $ 
 * $Date: 2000/11/21 21:39:36 $
 */

#include <stdio.h>
#include <alloca.h>
#include <stdlib.h>
#ifndef __linux__
#include <bstring.h>
#endif  /* __linux__ */
#include <math.h>
#include <Performer/pfutil.h>

/* #define	STATS */

#define ELTS_PER_BUCKET		1

#define SIZEOF_ELT(ht)	     					\
        ((int)sizeof(pfuHashElt) + (ht)->eltSize)

#define INDEX_ELT(ht,b,i)    					\
        ((pfuHashElt*)((char*) (b)->elts + (i)*SIZEOF_ELT(ht)))

#define COPY_ELT(ht,dst,e)	{				 \
	(dst)->listIndex = (e)->listIndex; (dst)->key = (e)->key;\
        (dst)->id = (e)->id; (dst)->userData = (e)->userData; \
    	bcopy((char*)(e)->data, (char*)(dst)->data, (int)((ht)->eltSize)); \
}

static pfuHashBucket* 	newBucket(pfuHashTable *ht);
static void		delBucket(pfuHashBucket *b);
static unsigned int	getKey(pfuHashTable *ht, pfuHashElt *elt);

static float		CreaseTol = .2f; 	/* acos(~78 degrees) */


	/*------------------------------------------------------*/

pfuHashTable*
pfuNewHTable(int numb, int eltsize, void *arena)
{
    int		realeltsize, i, j, bucketSize;
    pfuHashBucket 	*b, *bucket;
    pfuHashTable	*ht;

    /* eltSize must be multiple of sizeof(b) bytes */
    realeltsize = eltsize;
    if (eltsize & (sizeof(b)-1))
    {
        eltsize += sizeof(b) - eltsize&(sizeof(b)-1);
    }

    numb = pfuCalcHashSize(numb);

    ht = (pfuHashTable*) pfCalloc(1, (unsigned int)sizeof(pfuHashTable), arena);

    ht->numBuckets = numb;
    ht->buckets = (pfuHashBucket**)
	pfMalloc((unsigned int)(sizeof(pfuHashBucket*)*numb), arena);
    ht->eltSize = eltsize;
    ht->realeltSize = realeltsize;
    ht->arena = arena;
	
    /* 
     * Malloc buckets in one big chunk for faster mallocing and better
     * memory localization.
     */
    bucketSize = (int)sizeof(pfuHashBucket) + ELTS_PER_BUCKET * SIZEOF_ELT(ht);
    b = (pfuHashBucket*)pfCalloc((unsigned int)numb, (unsigned int)bucketSize, ht->arena);

    for(i=0; i<numb; i++)
    {
	bucket = (pfuHashBucket*) ((char*)b + i*bucketSize);
	bucket->elts = (pfuHashElt*) ((char*)bucket + sizeof(pfuHashBucket));

    	for(j=0; j<ELTS_PER_BUCKET; j++)
	    INDEX_ELT(ht, bucket, j)->data = 
		(void*) ((char*)INDEX_ELT(ht, bucket, j) + sizeof(pfuHashElt));
					 	
	ht->buckets[i] = bucket;
    }

    ht->list = (pfuHashElt**) pfCalloc((unsigned int)numb, sizeof(pfuHashElt*), arena);
    ht->listCount = 0;
    ht->listAvail = numb;

    return ht;
}

void
pfuDelHTable(pfuHashTable* ht)
{
    int	i;

#ifdef STATS
    unsigned int minOccupancy = ~0;
    unsigned int maxOccupancy =  0;
    unsigned int totOccupancy =  0;
#endif

    for(i=0; i<ht->numBuckets; i++)
    {
#ifdef STATS
	pfuHashBucket	*b;
	unsigned int minOcc = ~0;
	unsigned int maxOcc =  0;

	b = ht->buckets[i];
	if (b)
	{
	    int chainSize = 0;
	    while (b)
	    {
		chainSize += b->nelts;
		b = b->next;
	    }

	    if (minOccupancy > chainSize)
		minOccupancy = chainSize;
	    if (maxOccupancy < chainSize)
		maxOccupancy = chainSize;
	    totOccupancy += chainSize;
	}
#endif

	if (ht->buckets[i]->next)
	    delBucket(ht->buckets[i]->next);
    }
#ifdef STATS
    pfNotify(PFNFY_ALWAYS, PFNFY_RESOURCE, 
	"pfuDelHTable: tot %8d, size %5d, min %3d, max %4d, load %6.4f", 
	    totOccupancy,
	    ht->numBuckets, 
	    minOccupancy,
	    maxOccupancy,
	    (double)totOccupancy/(double)ht->numBuckets);
#endif

    pfFree(ht->buckets[0]);
    pfFree(ht->list);
    pfFree(ht->buckets);
    pfFree(ht);
}

static void
delBucket(pfuHashBucket *b)
{
    pfuHashBucket 	*next;

    while(b)
    {
	next = b->next;
	pfFree(b);
	b = next;
    }
}

static pfuHashBucket*
newBucket(pfuHashTable *ht)
{
    pfuHashBucket* 	b;
    int		i;

    b = (pfuHashBucket*)pfCalloc(1, (unsigned int)(sizeof(pfuHashBucket) + ELTS_PER_BUCKET * 
					SIZEOF_ELT(ht)), ht->arena);

    b->elts = (pfuHashElt*) ((char*)b + sizeof(pfuHashBucket));

    for(i=0; i<ELTS_PER_BUCKET; i++)
	INDEX_ELT(ht, b, i)->data = (void*) ((char*)INDEX_ELT(ht, b, i) 
					 	+ sizeof(pfuHashElt));
					
    return b;
}

void
pfuResetHTable(pfuHashTable *ht)
{
    int 		i;
    pfuHashBucket	*b;

    for (i=0; i<ht->numBuckets; i++)
    {
	b = ht->buckets[i];
	while (b)
	{
	    b->nelts = 0;
	    b = b->next;
	}
    }
    ht->listCount = 0;
}

int
pfuFindHash(pfuHashTable *ht, pfuHashElt *elt)
{
    int 		i, nlongs, *ld0;
    pfuHashElt		*dst;

    nlongs = ht->realeltSize >> 2;
    ld0 = (int*)elt->data;

    for (i=0; i<ht->listCount; i++)
    {
	int	j, *ld1;

	dst = ht->list[i];

	if(elt->key != dst->key)
	    continue;

    	ld1 = (int*)dst->data;

	for(j=0; j<nlongs; j++)
	{
	    if(ld0[j] != ld1[j])
	         break;
	}
	    
	/* Found */
	if(j == nlongs)
	    return 1;
    }
    return 0;
}

int
pfuRemoveHash(pfuHashTable *ht, pfuHashElt *elt)
{
    unsigned int 		index;
    pfuHashBucket	*b;
    pfuHashElt		*search;

    elt->key = getKey(ht, elt);
    index = elt->key % ht->numBuckets;

    b = ht->buckets[index];

    if(b->nelts == 0)
	return 0;
    else
    {
	int	i, j, n, nlongs, *ld0, *ld1;

	nlongs = ht->realeltSize >> 2;
    	ld0 = (int*)elt->data;

        while(b != NULL)
        {
	    n = b->nelts; 

	    for(i=0; i<n; i++)
	    {
	        search = INDEX_ELT(ht, b, i);

	        if(elt->key != search->key)
	    	    continue;

    	        ld1 = (int*)search->data;

	        for(j=0; j<nlongs; j++)
	        {
	    	    if(ld0[j] != ld1[j])
	    	        break;
	        }
	    
	        /* Found */
	        if(j == nlongs)
		{
		    while (1)
		    {
                        int        k;
                        pfuHashElt   *src, *dst;

                        /* Shift down elements in bucket */
    		    	for(k=i; k<b->nelts-1; k++)
			{
                            dst = INDEX_ELT(ht, b, k); 
                            src = INDEX_ELT(ht, b, k+1);
			    COPY_ELT(ht, dst, src);
                                         
			}
			if (b->next)
			{
                            dst = INDEX_ELT(ht, b, ELTS_PER_BUCKET-1);
                            src = INDEX_ELT(ht, b->next, 0);
			    COPY_ELT(ht, dst, src);
	                                 
                            b = b->next;
                            i = 0;
			}
                        else
                        {
			    /* Put end of list into removed element's spot */
			    ht->list[--ht->listCount]->listIndex = 
							search->listIndex;
			    ht->list[search->listIndex] = 
						ht->list[ht->listCount];

                            b->nelts--;
                            return 1;
                        }
                    }
		}
	    }
	
	    if(n < ELTS_PER_BUCKET)
		break;
            b = b->next;
	}

        /* Element not in bucket */
	return 0;
    }
}
    


pfuHashElt*
pfuEnterHash(pfuHashTable *ht, pfuHashElt *elt)
{
    unsigned int 		index;
    pfuHashBucket	*b, *tmp;
    pfuHashElt		*dst;

    elt->key = getKey(ht, elt);
    index = elt->key % ht->numBuckets;

    b = ht->buckets[index];

    if(b->nelts == 0)
    {
	if(ht->listCount >= ht->listAvail)
	{
	    ht->listAvail <<= 1;
	    ht->list = (pfuHashElt**) pfRealloc(ht->list, 
					(unsigned int)(ht->listAvail * sizeof(pfuHashElt*)));
	}

	dst = INDEX_ELT(ht, b, 0);
	elt->listIndex = ht->listCount;
	ht->list[ht->listCount++] = dst;
	COPY_ELT(ht, dst, elt);

    	b->nelts++;
    	return NULL;
    }
    else 
    {
	int	i, j, n, nlongs, *ld0, *ld1;

	nlongs = ht->realeltSize >> 2;
    	ld0 = (int*)elt->data;

        while(b != NULL)
        {
	    n = b->nelts; 

	    for(i=0; i<n; i++)
	    {
	        dst = INDEX_ELT(ht, b, i);

	        if(elt->key != dst->key)
	    	    continue;

    	        ld1 = (int*)dst->data;

	        for(j=0; j<nlongs; j++)
	        {
	    	    if(ld0[j] != ld1[j])
	    	        break;
	        }
	    
	        /* Found */
	        if(j == nlongs)
	    	    return dst;
	    }
	
	    if(n < ELTS_PER_BUCKET)
		break;
	    tmp = b;
            b = b->next;
        }
        
        /* Element not in bucket */

        /* Link new bucket if necessary */
        if(b == NULL)
        {
            b = newBucket(ht);
            tmp->next = b;
        }

        /* Copy element into bucket */
	dst = INDEX_ELT(ht, b, b->nelts);
	COPY_ELT(ht, dst, elt);

        /* Add element to flat list */
	if(ht->listCount >= ht->listAvail)
	{
	    ht->listAvail <<= 1;
	    ht->list = (pfuHashElt**) pfRealloc(ht->list, 
					(unsigned int)(ht->listAvail * sizeof(pfuHashElt*)));
	}
	dst->listIndex = ht->listCount;
	ht->list[ht->listCount++] = dst;

	b->nelts++;
	return NULL;
    }
}

/*
 *  Obscure, simple, fast, and TESTED hash function
 */
static unsigned int
getKey(pfuHashTable *ht, pfuHashElt *elt)
{
    unsigned int   size = ht->realeltSize;
    unsigned char *data = (unsigned char *)elt->data;
    unsigned int   hash = 0;
    unsigned int   wrap = 0;

    while (size--)
    {
	hash = (hash << 4) + *data++;
	wrap =  hash & 0xf0000000;
	if (wrap != 0)
	    hash ^= wrap >> 24;
    }
    return hash;
}

/*
 *  Division method of table indexing needs prime table size
 */
int
pfuCalcHashSize(int size)
{
    /* prime[i] is largest prime less than 2^(i+2) */
    static const int prime[] =
    {
             3, 
	     7, 
	    13, 
	    31,
            61, 
	   127, 
	   251, 
	   509,
          1021, 
	  2039, 
	  4093, 
	  8191,
         16381, 
	 32749, 
	 65521, 
	131071,
        262139, 
	524287, 
       1048573, 
       2097143,
       4194301, 
       8388593, 
      16777213, 
      33554393,
      67108859, 
     134217689, 
     268435399, 
     536870909,
    1073741789, 
    2147483647
    };
    static const int primeCount = sizeof(prime)/sizeof(prime[0]);

    int	i		= 0;
    int	hashSize	= 0;

    /* find prime near next power of two larger than size */
    while (i < primeCount)
	if ((hashSize = prime[i++]) >= size)
	    break;

    /* return chosen size */
    return hashSize;
}

static int
hashGSetVerts(pfGeoSet *gset, int computeNorms)
{
    int            i, j, n, nprims, vcount, vicount;
    pfVec3         *coords, *newCoords = NULL, *normals, *newNormals = NULL;
    pfVec2         *texCoords[PF_MAX_TEXTURES], 
		   *newTexCoords[PF_MAX_TEXTURES];
    pfVec4         *colors,  *newColors = NULL;
    unsigned short         *vindex, *tindex[PF_MAX_TEXTURES], *nindex,  *cindex;
    int            cbind, nbind, tbind[PF_MAX_TEXTURES], coff, noff, 
		   toff[PF_MAX_TEXTURES + 1];
    void	    *arena = pfGetArena(gset);
    pfuHashElt	    elt, *nelt;
    pfuHashTable    *ht;
    float           vert[3 + 2 + 3 + 4];
    pfList	    *triList;
    pfVec3	    *triNorms;
    int		    vertsPerPrim, primType;
    int		    t, numTextures;
    int		    texture_coord_size;

    for (t = 0 ; t < PF_MAX_TEXTURES ; t ++)
	newTexCoords[t] = NULL;

    numTextures = pfGetGSetNumTextures(gset);

    primType = pfGetGSetPrimType(gset);
    switch (primType) {
    case PFGS_QUADS:
	vertsPerPrim = 4;
	break;
    case PFGS_TRIS:
	vertsPerPrim = 3;
	break;
    case PFGS_LINES:
	vertsPerPrim = 2;
	break;
    case PFGS_POINTS:
	vertsPerPrim = 1;
	break;
    default:
	return 0;
    }

    nprims = pfGetGSetNumPrims(gset);
    pfGetGSetAttrLists(gset, PFGS_COORD3, (void **) &coords, &vindex);
    pfGetGSetAttrLists(gset, PFGS_COLOR4, (void **) &colors, &cindex);
    for (t = 0 ; t < numTextures ; t ++)
        pfGetGSetMultiAttrLists(gset, PFGS_TEXCOORD2, t,
                                (void**)&texCoords[t], &tindex[t]);
    pfGetGSetAttrLists(gset, PFGS_NORMAL3, (void **) &normals, &nindex);

    cbind = pfGetGSetAttrBind(gset, PFGS_COLOR4);
    nbind = pfGetGSetAttrBind(gset, PFGS_NORMAL3);
    for (t = 0 ; t < numTextures ; t ++)
        tbind[t] = pfGetGSetMultiAttrBind(gset, PFGS_TEXCOORD2, t);


    /* "Convert" nonindexed geoset into indexed */
    if (vindex == NULL)
    {
	if (nprims * vertsPerPrim > 0xffff)
	{
	    pfNotify(PFNFY_WARN, PFNFY_RESOURCE, "pfdHashGSetVerts() "
		     "Cannot hash verts. pfGeoSet has too many vertices (%d)"
		     "to fit into ushort index list.", nprims * vertsPerPrim);
	    return 0;
	}

	vindex = (ushort *)pfMalloc((uint)(sizeof(ushort) * 
					   nprims * vertsPerPrim), arena);
					    
	for (i=0; i<vertsPerPrim*nprims; i++)
	    vindex[i] = (ushort)i;

	if (nbind == PFGS_PER_VERTEX)
	    nindex = vindex;
	else if (nbind == PFGS_PER_PRIM)
	{
	    nindex = (ushort *)pfMalloc((uint)(sizeof(ushort) * nprims), arena);
	    for (i=0; i<nprims; i++)
		nindex[i] = (ushort)i;
	}
	else if (nbind == PFGS_OVERALL)
	{
	    nindex = (ushort *)pfMalloc((uint)sizeof(ushort), arena);
	    nindex[0] = 0;
	}
	
	if (cbind == PFGS_PER_VERTEX)
	    cindex = vindex;
	else if (cbind == PFGS_PER_PRIM)
	{
	    cindex = (ushort *)pfMalloc((uint)(sizeof(ushort) * nprims), arena);
	    for (i=0; i<nprims; i++)
		cindex[i] = (ushort)i;
	}
	else if (cbind == PFGS_OVERALL)
	{
	    cindex = (ushort *)pfMalloc((uint)sizeof(ushort), arena);
	    cindex[0] = 0;
	}

	for (t = 0 ; t < numTextures ; t ++)
	    if (tbind[t] == PFGS_PER_VERTEX)
		tindex[t] = vindex;

	pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, coords, vindex);
        pfGSetAttr(gset, PFGS_NORMAL3, nbind, normals, nindex);
	for (t = 0 ; t < numTextures ; t ++)
	    pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, tbind[t], 
				  texCoords[t], tindex[t]);
        pfGSetAttr(gset, PFGS_COLOR4, cbind, colors, cindex);
    }
    /* 
     * Return success if all PER_VERTEX attributes already share the
     * same index list.
    */
    else 
    {
	int	got_unique_texture;

	got_unique_texture = 0;
	for (t = 0 ; t < numTextures ; t ++)
	    if ((tbind[t] == PFGS_PER_VERTEX) && (vindex != tindex[t]))
		got_unique_texture = 1;

	if (!((cbind == PFGS_PER_VERTEX && cindex != vindex) ||
	       (got_unique_texture) ||
	       (nbind == PFGS_PER_VERTEX && nindex != vindex)))
	return 1;
    }

    texture_coord_size = 0;

    for (t = 0 ; t < numTextures ; t ++)
	if (tbind[t] == PFGS_PER_VERTEX)
	    texture_coord_size += 2;

    ht = pfuNewHTable(nprims,  (3 + 
			   texture_coord_size + 
			   (nbind == PFGS_PER_VERTEX)*3 + 
			   (cbind == PFGS_PER_VERTEX)*4)
		  * (int)sizeof(float), NULL);

    /* 
     *	Compute offsets for all available texture units.
     *
     *  Offset of texture-unit[numTextures] is the offset of the element
     *  following textures (noff).
     */
    toff[0] = 3;
    for (t = 1 ; t <= numTextures ; t ++)
	toff[t] = toff[t - 1] + (tbind[t - 1] == PFGS_PER_VERTEX) * 2;

    noff = toff[numTextures];
    coff = noff + (nbind == PFGS_PER_VERTEX) * 3;

    elt.data = (void *) vert;

    if (computeNorms && primType == PFGS_TRIS)
	triList = pfNewList(sizeof(void*), 4, NULL);

    /* Enter all vertices into hash table */
    vcount = vicount = 0;
    for (i = 0; i < nprims; i++)
    {
	for (j = 0; j < vertsPerPrim; j++)
	{
	    int	v = vicount + j;

	    PFCOPY_VEC3(vert, coords[vindex[v]]);
	    for (t = 0 ; t < numTextures ; t ++)
		if (tbind[t] == PFGS_PER_VERTEX)
		    PFCOPY_VEC2(vert + toff[t], texCoords[t][tindex[t][v]]);
	    if (nbind == PFGS_PER_VERTEX)
		PFCOPY_VEC3(vert + noff, normals[nindex[v]]);
	    if (cbind == PFGS_PER_VERTEX)
		PFCOPY_VEC4(vert + coff, colors[cindex[v]]);

	    elt.id = vcount;
	    elt.userData = triList;
	    nelt = pfuEnterHash(ht, &elt);

	    if (nelt != NULL)	/* Found vertex match */
	    {
		if (computeNorms && primType == PFGS_TRIS)
		    pfAdd((pfList*) nelt->userData, (void*)i);
		vindex[vicount+j] = (ushort)nelt->id;
	    }
	    else
	    {
		vindex[vicount+j] = (ushort)vcount++;
		if (computeNorms && primType == PFGS_TRIS)
		{
		    pfAdd(triList, (void*)i);
		    triList = pfNewList(sizeof(void*), 4, NULL);
		}
	    }
	}
	vicount += vertsPerPrim;

#ifdef	STATS
	if ((i % 128) == 0)
	{
	    pfNotify(PFNFY_INTERNAL_DEBUG, PFNFY_RESOURCE, "pfuHashGSetVerts: %.2f%% hash", 
		(float)i / (float)nprims);
	}
#endif
    }

    n = ht->listCount;

    if (n >= (1<<16))
	pfNotify(PFNFY_WARN, PFNFY_RESOURCE, "pfuHashGSetVerts: Too many vertices.");

    newCoords = (pfVec3 *) pfMalloc((uint)(sizeof(pfVec3) * n), 
				    arena);
    pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX, newCoords, vindex);
    pfDelete(coords);

    for (t = 0 ; t < numTextures ; t ++)
    {
	if (tbind[t] == PFGS_PER_VERTEX)
	{
	    newTexCoords[t] = (pfVec2 *) pfMalloc((uint)(sizeof(pfVec2) * n),
					       arena);
	    pfGSetMultiAttr(gset, PFGS_TEXCOORD2, t, PFGS_PER_VERTEX,
		       newTexCoords[t], vindex);
	    pfDelete(texCoords[t]);
	    if (tindex[t] != vindex)
		pfDelete(tindex[t]);
	}
    }
    if (nbind == PFGS_PER_VERTEX || (computeNorms && primType == PFGS_TRIS))
    {
	newNormals = (pfVec3 *) pfMalloc((uint)(sizeof(pfVec3) * n), 
					 arena);
	pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX,
		   newNormals, vindex);
	if (normals)
	    pfDelete(normals);
	if (nindex && nindex != vindex)
	    pfDelete(nindex);
    }
    if (cbind == PFGS_PER_VERTEX)
    {
	newColors = (pfVec4 *) pfMalloc((uint)(sizeof(pfVec4) * n), 
					arena);
	pfGSetAttr(gset, PFGS_COLOR4, PFGS_PER_VERTEX,
		   newColors, vindex);
	pfDelete(colors);
	if (cindex != vindex)
	    pfDelete(cindex);
    }
    /* Fill in new per-vertex attribute arrays shared attribute values */
    for (i = 0; i < n; i++)
    {
	float           *f =(float *) ht->list[i]->data;
	int		id = ht->list[i]->id;

	pfCopyVec3(newCoords[id], f);

	for (t = 0 ; t < numTextures ; t ++)
	    if (tbind[t] == PFGS_PER_VERTEX)
		pfCopyVec2(newTexCoords[t][id], f + toff[t]);

	if (nbind == PFGS_PER_VERTEX)
	    pfCopyVec3(newNormals[id], f + noff);

	if (cbind == PFGS_PER_VERTEX)
	    pfCopyVec4(newColors[id], f + coff);
    }

    if (computeNorms && primType == PFGS_TRIS)
    {
	int	extraCount = 0, finalCount;
	int	*extraCoords;
	pfVec3	*extraNormals, *finalNormals, *finalCoords;

	triNorms = (pfVec3*) alloca((int)sizeof(pfVec3) * nprims);
	extraCoords = (int*) alloca((int)sizeof(int) * vertsPerPrim);
	extraNormals = (pfVec3*) alloca((int)sizeof(pfVec3) * vertsPerPrim);

	/* Compute triangle face normals */
	for (i=0; i<nprims; i++)
	{
	    pfVec3	v0, v1;

	    PFSUB_VEC3(v0, newCoords[vindex[3*i+1]], newCoords[vindex[3*i+0]]);
	    PFSUB_VEC3(v1, newCoords[vindex[3*i+2]], newCoords[vindex[3*i+0]]);
	    pfCrossVec3(triNorms[i], v0, v1);
	    pfNormalizeVec3(triNorms[i]);
	}

	/* For each unique coordinate */
	for (i=0; i<ht->listCount; i++)
	{
	    pfList	*tlist;
	    int		j, nt, nnorms=1, split, vertId, newVertId;
	    float	s;
	    pfVec3	nsum;

	    /* Grab list of triangles sharing this vertex */
	    tlist = (pfList*) ht->list[i]->userData;
	    nt = pfGetNum(tlist);

	    vertId = ht->list[i]->id;
	    split = 0;

	    /* For each triangle sharing this vertex */
	    for (j=0; j<nt; j++)
	    {
		int	t, k;

		/* Grab first tri on list */
		t = (int)pfGet(tlist, j);

		/* Remove tri from vertex list */
		pfRemoveIndex(tlist, j);
		j--;
		nt--;

		if (split)
		{
		    int		tt=t*3;

		    /* Must create new vertex */
		    extraCoords[extraCount] = vertId;
		    newVertId = vcount + extraCount++;

		    if (vindex[tt] == vertId)
			vindex[tt] = newVertId;
		    else if (vindex[tt+1] == vertId)
			vindex[tt+1] = newVertId;
		    else if (vindex[tt+2] == vertId)
			vindex[tt+2] = newVertId;
		}

		PFCOPY_VEC3(nsum, triNorms[t]);

		/* For every other triangle sharing this vertex */
		for (k=0; k<nt; k++)
		{
		    int	othert;

		    /* Get other triangle */
		    othert = (int)pfGet(tlist, k);
		    
		    /* Don't accumulate normal if too different */
		    if (PFDOT_VEC3(triNorms[t], triNorms[othert]) > CreaseTol)
		    {
			PFADD_VEC3(nsum, nsum, triNorms[t]);
			nnorms++;

			/* Remove combined tri from list */
			pfRemoveIndex(tlist, k);
			k--;
			nt--;

			if (split)
			{
			    int		tt=othert*3;

			    if (vindex[tt] == vertId)
				vindex[tt] = newVertId;
			    else if (vindex[tt+1] == vertId)
				vindex[tt+1] = newVertId;
			    else if (vindex[tt+2] == vertId)
				vindex[tt+2] = newVertId;
			}
		    }
		}
		s = 1.0f / (float)nnorms;
		
		if (split++)
		{
		    pfScaleVec3(extraNormals[newVertId - vcount], s, nsum);
		    pfNormalizeVec3(extraNormals[newVertId - vcount]);
		}
		else
		{
		    pfScaleVec3(newNormals[vertId], s, nsum);
		    pfNormalizeVec3(newNormals[vertId]);
		}
	    }
	    
	    pfDelete(tlist);
 	}
	
	finalCount = vcount + extraCount;
	finalNormals = (pfVec3 *) pfMalloc((uint)
			   (sizeof(pfVec3) * finalCount), arena);
	finalCoords = (pfVec3 *) pfMalloc((uint)
			   (sizeof(pfVec3) * finalCount), arena);

	for (i=0; i<vcount; i++)
	{
	    PFCOPY_VEC3(finalCoords[i], newCoords[i]);
	    PFCOPY_VEC3(finalNormals[i], newNormals[i]);
	}

	for (j=0, i=vcount; i<finalCount; i++, j++)
	{
	    PFCOPY_VEC3(finalCoords[i], newCoords[extraCoords[j]]);
	    PFCOPY_VEC3(finalNormals[i], extraNormals[j]);
	}

	pfGSetAttr(gset, PFGS_NORMAL3, PFGS_PER_VERTEX,
		   finalNormals, vindex);

	pfGSetAttr(gset, PFGS_COORD3, PFGS_PER_VERTEX,
		   finalCoords, vindex);

	pfDelete(triList);
	pfDelete(newNormals);
	pfDelete(newCoords);;
    }

    pfuDelHTable(ht);

    return 1;
}

int
pfuHashGSetVerts(pfGeoSet *gset)
{
    return hashGSetVerts(gset, 0);
}

int
pfuComputeGSetNorms(pfGeoSet *gset)
{
    return hashGSetVerts(gset, 1);
}