]> git.xonotic.org Git - xonotic/netradiant.git/blobdiff - libs/picomodel/picomodel.c
Wean off #define
[xonotic/netradiant.git] / libs / picomodel / picomodel.c
index 767fc74f1f61d59bcd0605ad6288fb4530f9c10f..311f6de760c7791bb9e94f717d481a192c46dfdc 100644 (file)
 
    ----------------------------------------------------------------------------- */
 
-
-
-/* marker */
-#define PICOMODEL_C
-
-
-
 /* dependencies */
 #include "picointernal.h"
 
@@ -111,7 +104,7 @@ void PicoSetFreeFunc( void ( *func )( void* ) ){
    sets the ptr to the file load function
  */
 
-void PicoSetLoadFileFunc( void ( *func )( char*, unsigned char**, int* ) ){
+void PicoSetLoadFileFunc( void ( *func )( const char*, unsigned char**, int* ) ){
        if ( func != NULL ) {
                _pico_ptr_load_file = func;
        }
@@ -145,17 +138,57 @@ void PicoSetPrintFunc( void ( *func )( int, const char* ) ){
 
 
 
+picoModel_t *PicoModuleLoadModel( const picoModule_t* pm, const char* fileName, picoByte_t* buffer, int bufSize, int frameNum ){
+       char                *modelFileName, *remapFileName;
+
+       /* see whether this module can load the model file or not */
+       if ( pm->canload( fileName, buffer, bufSize ) == PICO_PMV_OK ) {
+               /* use loader provided by module to read the model data */
+               picoModel_t* model = pm->load( fileName, frameNum, buffer, bufSize );
+               if ( model == NULL ) {
+                       _pico_free_file( buffer );
+                       return NULL;
+               }
+
+               /* assign pointer to file format module */
+               model->module = pm;
+
+               /* get model file name */
+               modelFileName = PicoGetModelFileName( model );
+
+               /* apply model remappings from <model>.remap */
+               if ( strlen( modelFileName ) ) {
+                       /* alloc copy of model file name */
+                       remapFileName = _pico_alloc( strlen( modelFileName ) + 20 );
+                       if ( remapFileName != NULL ) {
+                               /* copy model file name and change extension */
+                               strcpy( remapFileName, modelFileName );
+                               _pico_setfext( remapFileName, "remap" );
+
+                               /* try to remap model; we don't handle the result */
+                               PicoRemapModel( model, remapFileName );
+
+                               /* free the remap file name string */
+                               _pico_free( remapFileName );
+                       }
+               }
+
+               return model;
+       }
+
+       return NULL;
+}
+
 /*
    PicoLoadModel()
    the meat and potatoes function
  */
 
-picoModel_t *PicoLoadModel( char *fileName, int frameNum ){
+picoModel_t *PicoLoadModel( const char *fileName, int frameNum ){
        const picoModule_t  **modules, *pm;
        picoModel_t         *model;
        picoByte_t          *buffer;
        int bufSize;
-       char                *modelFileName, *remapFileName;
 
 
        /* init */
@@ -194,38 +227,8 @@ picoModel_t *PicoLoadModel( char *fileName, int frameNum ){
                        continue;
                }
 
-               /* see whether this module can load the model file or not */
-               if ( pm->canload( fileName, buffer, bufSize ) == PICO_PMV_OK ) {
-                       /* use loader provided by module to read the model data */
-                       model = pm->load( fileName, frameNum, buffer, bufSize );
-                       if ( model == NULL ) {
-                               _pico_free_file( buffer );
-                               return NULL;
-                       }
-
-                       /* assign pointer to file format module */
-                       model->module = pm;
-
-                       /* get model file name */
-                       modelFileName = PicoGetModelFileName( model );
-
-                       /* apply model remappings from <model>.remap */
-                       if ( strlen( modelFileName ) ) {
-                               /* alloc copy of model file name */
-                               remapFileName = _pico_alloc( strlen( modelFileName ) + 20 );
-                               if ( remapFileName != NULL ) {
-                                       /* copy model file name and change extension */
-                                       strcpy( remapFileName, modelFileName );
-                                       _pico_setfext( remapFileName, "remap" );
-
-                                       /* try to remap model; we don't handle the result */
-                                       PicoRemapModel( model, remapFileName );
-
-                                       /* free the remap file name string */
-                                       _pico_free( remapFileName );
-                               }
-                       }
-
+               model = PicoModuleLoadModel( pm, fileName, buffer, bufSize, frameNum );
+               if ( model != NULL ) {
                        /* model was loaded, so break out of loop */
                        break;
                }
@@ -240,6 +243,39 @@ picoModel_t *PicoLoadModel( char *fileName, int frameNum ){
        return model;
 }
 
+picoModel_t *PicoModuleLoadModelStream( const picoModule_t* module, void* inputStream, PicoInputStreamReadFunc inputStreamRead, size_t streamLength, int frameNum, const char *fileName ){
+       picoModel_t         *model;
+       picoByte_t          *buffer;
+       int bufSize;
+
+
+       /* init */
+       model = NULL;
+
+       if ( inputStream == NULL ) {
+               _pico_printf( PICO_ERROR, "PicoLoadModel: invalid input stream (inputStream == NULL)" );
+               return NULL;
+       }
+
+       if ( inputStreamRead == NULL ) {
+               _pico_printf( PICO_ERROR, "PicoLoadModel: invalid input stream (inputStreamRead == NULL)" );
+               return NULL;
+       }
+
+       buffer = _pico_alloc( streamLength + 1 );
+
+       bufSize = (int)inputStreamRead( inputStream, buffer, streamLength );
+       buffer[bufSize] = '\0';
+
+       model = PicoModuleLoadModel( module, fileName, buffer, bufSize, frameNum );
+
+       if ( model != 0 ) {
+               _pico_free( buffer );
+       }
+
+       /* return */
+       return model;
+}
 
 
 /* ----------------------------------------------------------------------------
@@ -294,6 +330,10 @@ void PicoFreeModel( picoModel_t *model ){
                _pico_free( model->name );
        }
 
+       if ( model->fileName ) {
+               _pico_free( model->fileName );
+       }
+
        /* free shaders */
        for ( i = 0; i < model->numShaders; i++ )
                PicoFreeShader( model->shader[ i ] );
@@ -392,10 +432,12 @@ picoShader_t *PicoNewShader( picoModel_t *model ){
                        _pico_free( shader );
                        return NULL;
                }
+
                /* attach */
                model->shader[ model->numShaders - 1 ] = shader;
                shader->model = model;
        }
+
        /* setup default shader colors */
        _pico_set_color( shader->ambientColor,0,0,0,0 );
        _pico_set_color( shader->diffuseColor,255,255,255,1 );
@@ -535,9 +577,14 @@ void PicoFreeSurface( picoSurface_t *surface ){
        /* free bits */
        _pico_free( surface->xyz );
        _pico_free( surface->normal );
+       _pico_free( surface->smoothingGroup );
        _pico_free( surface->index );
        _pico_free( surface->faceNormal );
 
+       if ( surface->name ) {
+               _pico_free( surface->name );
+       }
+
        /* free arrays */
        for ( i = 0; i < surface->numSTArrays; i++ )
                _pico_free( surface->st[ i ] );
@@ -591,6 +638,9 @@ int PicoAdjustSurface( picoSurface_t *surface, int numVertexes, int numSTArrays,
                if ( !_pico_realloc( (void *) &surface->normal, surface->numVertexes * sizeof( *surface->normal ), surface->maxVertexes * sizeof( *surface->normal ) ) ) {
                        return 0;
                }
+               if ( !_pico_realloc( (void *) &surface->smoothingGroup, surface->numVertexes * sizeof( *surface->smoothingGroup ), surface->maxVertexes * sizeof( *surface->smoothingGroup ) ) ) {
+                       return 0;
+               }
                for ( i = 0; i < surface->numSTArrays; i++ )
                        if ( !_pico_realloc( (void*) &surface->st[ i ], surface->numVertexes * sizeof( *surface->st[ i ] ), surface->maxVertexes * sizeof( *surface->st[ i ] ) ) ) {
                                return 0;
@@ -712,7 +762,7 @@ picoSurface_t *PicoFindSurface(
    PicoSet*() Setter Functions
    ----------------------------------------------------------------------------*/
 
-void PicoSetModelName( picoModel_t *model, char *name ){
+void PicoSetModelName( picoModel_t *model, const char *name ){
        if ( model == NULL || name == NULL ) {
                return;
        }
@@ -720,12 +770,12 @@ void PicoSetModelName( picoModel_t *model, char *name ){
                _pico_free( model->name );
        }
 
-       model->name = _pico_clone_alloc( name,-1 );
+       model->name = _pico_clone_alloc( name );
 }
 
 
 
-void PicoSetModelFileName( picoModel_t *model, char *fileName ){
+void PicoSetModelFileName( picoModel_t *model, const char *fileName ){
        if ( model == NULL || fileName == NULL ) {
                return;
        }
@@ -733,7 +783,7 @@ void PicoSetModelFileName( picoModel_t *model, char *fileName ){
                _pico_free( model->fileName );
        }
 
-       model->fileName = _pico_clone_alloc( fileName,-1 );
+       model->fileName = _pico_clone_alloc( fileName );
 }
 
 
@@ -773,7 +823,7 @@ void PicoSetShaderName( picoShader_t *shader, char *name ){
                _pico_free( shader->name );
        }
 
-       shader->name = _pico_clone_alloc( name,-1 );
+       shader->name = _pico_clone_alloc( name );
 }
 
 
@@ -786,7 +836,7 @@ void PicoSetShaderMapName( picoShader_t *shader, char *mapName ){
                _pico_free( shader->mapName );
        }
 
-       shader->mapName = _pico_clone_alloc( mapName,-1 );
+       shader->mapName = _pico_clone_alloc( mapName );
 }
 
 
@@ -879,7 +929,7 @@ void PicoSetSurfaceType( picoSurface_t *surface, picoSurfaceType_t type ){
 
 
 
-void PicoSetSurfaceName( picoSurface_t *surface, char *name ){
+void PicoSetSurfaceName( picoSurface_t *surface, const char *name ){
        if ( surface == NULL || name == NULL ) {
                return;
        }
@@ -887,7 +937,7 @@ void PicoSetSurfaceName( picoSurface_t *surface, char *name ){
                _pico_free( surface->name );
        }
 
-       surface->name = _pico_clone_alloc( name,-1 );
+       surface->name = _pico_clone_alloc( name );
 }
 
 
@@ -991,6 +1041,17 @@ void PicoSetFaceNormal( picoSurface_t *surface, int num, picoVec3_t normal ){
 }
 
 
+void PicoSetSurfaceSmoothingGroup( picoSurface_t *surface, int num, picoIndex_t smoothingGroup ){
+       if ( num < 0 ) {
+               return;
+       }
+       if ( !PicoAdjustSurface( surface, num + 1, 0, 0, 0, 0 ) ) {
+               return;
+       }
+       surface->smoothingGroup[ num ] = smoothingGroup;
+}
+
+
 void PicoSetSurfaceSpecial( picoSurface_t *surface, int num, int special ){
        if ( surface == NULL || num < 0 || num >= PICO_MAX_SPECIAL ) {
                return;
@@ -1335,6 +1396,13 @@ picoVec_t *PicoGetFaceNormal( picoSurface_t *surface, int num ){
        return surface->faceNormal[ num ];
 }
 
+picoIndex_t PicoGetSurfaceSmoothingGroup( picoSurface_t *surface, int num ){
+       if ( surface == NULL || num < 0 || num > surface->numVertexes ) {
+               return -1;
+       }
+       return surface->smoothingGroup[ num ];
+}
+
 
 int PicoGetSurfaceSpecial( picoSurface_t *surface, int num ){
        if ( surface == NULL || num < 0 || num >= PICO_MAX_SPECIAL ) {
@@ -1351,7 +1419,7 @@ int PicoGetSurfaceSpecial( picoSurface_t *surface, int num ){
 
 /* hashtable code for faster vertex lookups */
 //#define HASHTABLE_SIZE 32768 // 2048                 /* power of 2, use & */
-#define HASHTABLE_SIZE 7919 // 32749 // 2039    /* prime, use % */
+const int HASHTABLE_SIZE = 7919; // 32749 // 2039    /* prime, use % */
 
 int PicoGetHashTableSize( void ){
        return HASHTABLE_SIZE;
@@ -1535,7 +1603,7 @@ picoVertexCombinationHash_t *PicoAddVertexCombinationToHashTable( picoVertexComb
    fixme: needs non-naive algorithm
  */
 
-int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t normal, int numSTs, picoVec2_t *st, int numColors, picoColor_t *color ){
+int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t normal, int numSTs, picoVec2_t *st, int numColors, picoColor_t *color, picoIndex_t smoothingGroup ){
        int i, j;
 
 
@@ -1557,6 +1625,11 @@ int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t
                        continue;
                }
 
+               /* check normal */
+               if ( surface->smoothingGroup[ i ] != smoothingGroup ) {
+                       continue;
+               }
+
                /* check st */
                if ( numSTs > 0 && st != NULL ) {
                        for ( j = 0; j < numSTs; j++ )
@@ -1593,129 +1666,306 @@ int PicoFindSurfaceVertexNum( picoSurface_t *surface, picoVec3_t xyz, picoVec3_t
 
 
 
-/*
-   PicoFixSurfaceNormals()
-   fixes broken normals (certain formats bork normals)
- */
 
-#define MAX_NORMAL_VOTES        128
-#define EQUAL_NORMAL_EPSILON    0.01
-#define BAD_NORMAL_EPSILON      0.5
+typedef struct _IndexArray IndexArray;
+struct _IndexArray
+{
+       picoIndex_t* data;
+       picoIndex_t* last;
+};
 
-void PicoFixSurfaceNormals( picoSurface_t *surface ){
-       int i, j, k, a, b, c, numVotes, faceIndex;
-       picoVec3_t votes[ MAX_NORMAL_VOTES ];
-       picoVec3_t      *normals, diff;
-       picoVec4_t plane;
+void indexarray_push_back( IndexArray* self, picoIndex_t value ){
+       *self->last++ = value;
+}
 
+size_t indexarray_size( IndexArray* self ){
+       return self->last - self->data;
+}
 
-       /* dummy check */
-       if ( surface == NULL || surface->numVertexes == 0 ) {
-               return;
-       }
+void indexarray_reserve( IndexArray* self, size_t size ){
+       self->data = self->last = _pico_calloc( size, sizeof( picoIndex_t ) );
+}
 
-       /* fixme: handle other surface types */
-       if ( surface->type != PICO_TRIANGLES ) {
-               return;
-       }
+void indexarray_clear( IndexArray* self ){
+       _pico_free( self->data );
+}
 
-       /* allocate normal storage */
-       normals = _pico_alloc( surface->numVertexes * sizeof( *normals ) );
-       if ( normals == NULL ) {
-               _pico_printf( PICO_ERROR, "PicoFixSurfaceNormals: Unable to allocate memory for temporary normal storage" );
-               return;
-       }
+typedef struct _BinaryTreeNode BinaryTreeNode;
+struct _BinaryTreeNode
+{
+       picoIndex_t left;
+       picoIndex_t right;
+};
 
-       /* zero it out */
-       memset( normals, 0, surface->numVertexes * sizeof( *normals ) );
+typedef struct _BinaryTree BinaryTree;
+struct _BinaryTree
+{
+       BinaryTreeNode* data;
+       BinaryTreeNode* last;
+};
 
-       /* walk vertex list */
-       for ( i = 0; i < surface->numVertexes; i++ )
-       {
-               /* zero out votes */
-               numVotes = 0;
+void binarytree_extend( BinaryTree* self ){
+       self->last->left = 0;
+       self->last->right = 0;
+       ++self->last;
+}
 
-               /* find all the triangles that reference this vertex */
-               for ( j = 0, faceIndex = 0; j < surface->numIndexes; j += 3, faceIndex++ )
-               {
-                       /* get triangle */
-                       a = surface->index[ j ];
-                       b = surface->index[ j + 1 ];
-                       c = surface->index[ j + 2 ];
+size_t binarytree_size( BinaryTree* self ){
+       return self->last - self->data;
+}
+
+void binarytree_reserve( BinaryTree* self, size_t size ){
+       self->data = self->last = _pico_calloc( size, sizeof( BinaryTreeNode ) );
+}
 
-                       /* ignore degenerate triangles */
-                       if ( a == b || b == c || c == a ) {
+void binarytree_clear( BinaryTree* self ){
+       _pico_free( self->data );
+}
+
+typedef int ( *LessFunc )( void*, picoIndex_t, picoIndex_t );
+
+typedef struct _UniqueIndices UniqueIndices;
+struct _UniqueIndices
+{
+       BinaryTree tree;
+       IndexArray indices;
+       LessFunc lessFunc;
+       void* lessData;
+};
+
+size_t UniqueIndices_size( UniqueIndices* self ){
+       return binarytree_size( &self->tree );
+}
+
+void UniqueIndices_reserve( UniqueIndices* self, size_t size ){
+       binarytree_reserve( &self->tree, size );
+       indexarray_reserve( &self->indices, size );
+}
+
+void UniqueIndices_init( UniqueIndices* self, LessFunc lessFunc, void* lessData ){
+       self->lessFunc = lessFunc;
+       self->lessData = lessData;
+}
+
+void UniqueIndices_destroy( UniqueIndices* self ){
+       binarytree_clear( &self->tree );
+       indexarray_clear( &self->indices );
+}
+
+
+picoIndex_t UniqueIndices_find_or_insert( UniqueIndices* self, picoIndex_t value ){
+       picoIndex_t index = 0;
+
+       for (;; )
+       {
+               if ( self->lessFunc( self->lessData, value, self->indices.data[index] ) ) {
+                       BinaryTreeNode* node = self->tree.data + index;
+                       if ( node->left != 0 ) {
+                               index = node->left;
                                continue;
                        }
-
-                       /* ignore indexes out of range */
-                       if ( a < 0 || a >= surface->numVertexes ||
-                                b < 0 || b >= surface->numVertexes ||
-                                c < 0 || c >= surface->numVertexes ) {
+                       else
+                       {
+                               node->left = (picoIndex_t)binarytree_size( &self->tree );
+                               binarytree_extend( &self->tree );
+                               indexarray_push_back( &self->indices, value );
+                               return node->left;
+                       }
+               }
+               if ( self->lessFunc( self->lessData, self->indices.data[index], value ) ) {
+                       BinaryTreeNode* node = self->tree.data + index;
+                       if ( node->right != 0 ) {
+                               index = node->right;
                                continue;
                        }
+                       else
+                       {
+                               node->right = (picoIndex_t)binarytree_size( &self->tree );
+                               binarytree_extend( &self->tree );
+                               indexarray_push_back( &self->indices, value );
+                               return node->right;
+                       }
+               }
 
-                       /* test triangle */
-                       if ( a == i || b == i || c == i ) {
-                               /* if this surface has face normals */
-                               if ( surface->numFaceNormals && faceIndex < surface->numFaceNormals ) {
-                                       _pico_copy_vec( surface->faceNormal[ faceIndex ], plane );
-                                       if ( plane[ 0 ] == 0.f && plane[ 1 ] == 0.f && plane[ 2 ] == 0.f ) {
-                                               /* if null normal, make plane from the 3 points */
-                                               if ( _pico_calc_plane( plane, surface->xyz[ a ], surface->xyz[ b ], surface->xyz[ c ] ) == 0 ) {
-                                                       continue;
-                                               }
-                                       }
-                               }
-                               /* make a plane from the 3 points */
-                               else if ( _pico_calc_plane( plane, surface->xyz[ a ], surface->xyz[ b ], surface->xyz[ c ] ) == 0 ) {
-                                       continue;
-                               }
+               return index;
+       }
+}
 
-                               /* see if this normal has already been voted */
-                               for ( k = 0; k < numVotes; k++ )
-                               {
-                                       _pico_subtract_vec( plane, votes[ k ], diff );
-                                       if ( fabs( diff[ 0 ] ) < EQUAL_NORMAL_EPSILON &&
-                                                fabs( diff[ 1 ] ) < EQUAL_NORMAL_EPSILON &&
-                                                fabs( diff[ 2 ] ) < EQUAL_NORMAL_EPSILON ) {
-                                               break;
-                                       }
-                               }
+picoIndex_t UniqueIndices_insert( UniqueIndices* self, picoIndex_t value ){
+       if ( self->tree.data == self->tree.last ) {
+               binarytree_extend( &self->tree );
+               indexarray_push_back( &self->indices, value );
+               return 0;
+       }
+       else
+       {
+               return UniqueIndices_find_or_insert( self, value );
+       }
+}
 
-                               /* add a new vote? */
-                               if ( k == numVotes && numVotes < MAX_NORMAL_VOTES ) {
-                                       _pico_copy_vec( plane, votes[ numVotes ] );
-                                       numVotes++;
-                               }
+typedef struct picoSmoothVertices_s picoSmoothVertices_t;
+struct picoSmoothVertices_s
+{
+       picoVec3_t* xyz;
+       picoIndex_t* smoothingGroups;
+};
+
+int lessSmoothVertex( void* data, picoIndex_t first, picoIndex_t second ){
+       picoSmoothVertices_t* smoothVertices = data;
+
+       if ( smoothVertices->xyz[first][0] != smoothVertices->xyz[second][0] ) {
+               return smoothVertices->xyz[first][0] < smoothVertices->xyz[second][0];
+       }
+       if ( smoothVertices->xyz[first][1] != smoothVertices->xyz[second][1] ) {
+               return smoothVertices->xyz[first][1] < smoothVertices->xyz[second][1];
+       }
+       if ( smoothVertices->xyz[first][2] != smoothVertices->xyz[second][2] ) {
+               return smoothVertices->xyz[first][2] < smoothVertices->xyz[second][2];
+       }
+       if ( smoothVertices->smoothingGroups[first] != smoothVertices->smoothingGroups[second] ) {
+               return smoothVertices->smoothingGroups[first] < smoothVertices->smoothingGroups[second];
+       }
+       return 0;
+}
+
+void _pico_vertices_combine_shared_normals( picoVec3_t* xyz, picoIndex_t* smoothingGroups, picoVec3_t* normals, picoIndex_t numVertices ){
+       UniqueIndices vertices;
+       IndexArray indices;
+       picoSmoothVertices_t smoothVertices = { xyz, smoothingGroups };
+       UniqueIndices_init( &vertices, lessSmoothVertex, &smoothVertices );
+       UniqueIndices_reserve( &vertices, numVertices );
+       indexarray_reserve( &indices, numVertices );
+
+
+       {
+               picoIndex_t i = 0;
+               for (; i < numVertices; ++i )
+               {
+                       size_t size = UniqueIndices_size( &vertices );
+                       picoIndex_t index = UniqueIndices_insert( &vertices, i );
+                       if ( (size_t)index != size ) {
+                               float* normal = normals[vertices.indices.data[index]];
+                               _pico_add_vec( normal, normals[i], normal );
                        }
+                       indexarray_push_back( &indices, index );
                }
+       }
 
-               /* tally votes */
-               if ( numVotes > 0 ) {
-                       /* create average normal */
-                       _pico_zero_vec( normals[ i ] );
-                       for ( k = 0; k < numVotes; k++ )
-                               _pico_add_vec( normals[ i ], votes[ k ], normals[ i ] );
-
-                       /* normalize it */
-                       if ( _pico_normalize_vec( normals[ i ] ) ) {
-                               /* test against actual normal */
-                               if ( fabs( _pico_dot_vec( normals[ i ], surface->normal[ i ] ) - 1 ) > BAD_NORMAL_EPSILON ) {
-                                       //%     printf( "Normal %8d: (%f %f %f) -> (%f %f %f)\n", i,
-                                       //%             surface->normal[ i ][ 0 ], surface->normal[ i ][ 1 ], surface->normal[ i ][ 2 ],
-                                       //%             normals[ i ][ 0 ], normals[ i ][ 1 ], normals[ i ][ 2 ] );
-                                       _pico_copy_vec( normals[ i ], surface->normal[ i ] );
+       {
+               picoIndex_t maxIndex = 0;
+               picoIndex_t* i = indices.data;
+               for (; i != indices.last; ++i )
+               {
+                       if ( *i <= maxIndex ) {
+                               _pico_copy_vec( normals[vertices.indices.data[*i]], normals[i - indices.data] );
+                       }
+                       else
+                       {
+                               maxIndex = *i;
+                       }
+               }
+       }
+
+       UniqueIndices_destroy( &vertices );
+       indexarray_clear( &indices );
+}
+
+typedef picoVec3_t* picoNormalIter_t;
+typedef picoIndex_t* picoIndexIter_t;
+
+#define THE_CROSSPRODUCTS_OF_ANY_PAIR_OF_EDGES_OF_A_GIVEN_TRIANGLE_ARE_EQUAL 1
+
+void _pico_triangles_generate_weighted_normals( picoIndexIter_t first, picoIndexIter_t end, picoVec3_t* xyz, picoVec3_t* normals ){
+       for (; first != end; first += 3 )
+       {
+#if ( THE_CROSSPRODUCTS_OF_ANY_PAIR_OF_EDGES_OF_A_GIVEN_TRIANGLE_ARE_EQUAL )
+               picoVec3_t weightedNormal;
+               {
+                       float* a = xyz[*( first + 0 )];
+                       float* b = xyz[*( first + 1 )];
+                       float* c = xyz[*( first + 2 )];
+                       picoVec3_t ba, ca;
+                       _pico_subtract_vec( b, a, ba );
+                       _pico_subtract_vec( c, a, ca );
+                       _pico_cross_vec( ca, ba, weightedNormal );
+               }
+#endif
+               {
+                       int j = 0;
+                       for (; j < 3; ++j )
+                       {
+                               float* normal = normals[*( first + j )];
+#if ( !THE_CROSSPRODUCTS_OF_ANY_PAIR_OF_EDGES_OF_A_GIVEN_TRIANGLE_ARE_EQUAL )
+                               picoVec3_t weightedNormal;
+                               {
+                                       float* a = xyz[*( first + ( ( j + 0 ) % 3 ) )];
+                                       float* b = xyz[*( first + ( ( j + 1 ) % 3 ) )];
+                                       float* c = xyz[*( first + ( ( j + 2 ) % 3 ) )];
+                                       picoVec3_t ba, ca;
+                                       _pico_subtract_vec( b, a, ba );
+                                       _pico_subtract_vec( c, a, ca );
+                                       _pico_cross_vec( ca, ba, weightedNormal );
                                }
+#endif
+                               _pico_add_vec( weightedNormal, normal, normal );
                        }
                }
        }
+}
 
-       /* free normal storage */
-       _pico_free( normals );
+void _pico_normals_zero( picoNormalIter_t first, picoNormalIter_t last ){
+       for (; first != last; ++first )
+       {
+               _pico_zero_vec( *first );
+       }
+}
+
+void _pico_normals_normalize( picoNormalIter_t first, picoNormalIter_t last ){
+       for (; first != last; ++first )
+       {
+               _pico_normalize_vec( *first );
+       }
 }
 
+double _pico_length_vec( picoVec3_t vec ){
+       return sqrt( vec[ 0 ] * vec[ 0 ] + vec[ 1 ] * vec[ 1 ] + vec[ 2 ] * vec[ 2 ] );
+}
 
+#define NORMAL_UNIT_LENGTH_EPSILON 0.01
+#define FLOAT_EQUAL_EPSILON( f, other, epsilon ) ( fabs( f - other ) < epsilon )
+
+int _pico_normal_is_unit_length( picoVec3_t normal ){
+       return FLOAT_EQUAL_EPSILON( _pico_length_vec( normal ), 1.0, NORMAL_UNIT_LENGTH_EPSILON );
+}
+
+int _pico_normal_within_tolerance( picoVec3_t normal, picoVec3_t other ){
+       return _pico_dot_vec( normal, other ) > 0.0f;
+}
+
+
+void _pico_normals_assign_generated_normals( picoNormalIter_t first, picoNormalIter_t last, picoNormalIter_t generated ){
+       for (; first != last; ++first, ++generated )
+       {
+               if ( !_pico_normal_is_unit_length( *first ) || !_pico_normal_within_tolerance( *first, *generated ) ) {
+                       _pico_copy_vec( *generated, *first );
+               }
+       }
+}
+
+void PicoFixSurfaceNormals( picoSurface_t* surface ){
+       picoVec3_t* normals = (picoVec3_t*)_pico_calloc( surface->numVertexes, sizeof( picoVec3_t ) );
+
+       _pico_normals_zero( normals, normals + surface->numVertexes );
+
+       _pico_triangles_generate_weighted_normals( surface->index, surface->index + surface->numIndexes, surface->xyz, normals );
+       _pico_vertices_combine_shared_normals( surface->xyz, surface->smoothingGroup, normals, surface->numVertexes );
+
+       _pico_normals_normalize( normals, normals + surface->numVertexes );
+
+       _pico_normals_assign_generated_normals( surface->normal, surface->normal + surface->numVertexes, normals );
+
+       _pico_free( normals );
+}
 
 
 /*
@@ -1799,7 +2049,7 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){
                                if ( !strlen( p->token ) ) {
                                        continue;
                                }
-                               materialName = _pico_clone_alloc( p->token,-1 );
+                               materialName = _pico_clone_alloc( p->token );
                                if ( materialName == NULL ) {
                                        _prm_error_return;
                                }
@@ -1857,7 +2107,7 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){
                        }
 
                        /* temporary copy of material name */
-                       tempMaterialName = _pico_clone_alloc( p->token,-1 );
+                       tempMaterialName = _pico_clone_alloc( p->token );
                        if ( tempMaterialName == NULL ) {
                                _prm_error_return;
                        }
@@ -1935,7 +2185,6 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){
                                        color[ 0 ] = (picoByte_t)v[ 0 ];
                                        color[ 1 ] = (picoByte_t)v[ 1 ];
                                        color[ 2 ] = (picoByte_t)v[ 2 ];
-                                       color[ 3 ] = 1;
 
                                        /* set new ambient color */
                                        PicoSetShaderAmbientColor( shader,color );
@@ -1954,7 +2203,6 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){
                                        color[ 0 ] = (picoByte_t)v[ 0 ];
                                        color[ 1 ] = (picoByte_t)v[ 1 ];
                                        color[ 2 ] = (picoByte_t)v[ 2 ];
-                                       color[ 3 ] = 1;
 
                                        /* set new ambient color */
                                        PicoSetShaderDiffuseColor( shader,color );
@@ -1973,7 +2221,6 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){
                                        color[ 0 ] = (picoByte_t)v[ 0 ];
                                        color[ 1 ] = (picoByte_t)v[ 1 ];
                                        color[ 2 ] = (picoByte_t)v[ 2 ];
-                                       color[ 3 ] = 1;
 
                                        /* set new ambient color */
                                        PicoSetShaderSpecularColor( shader,color );
@@ -2002,7 +2249,7 @@ int PicoRemapModel( picoModel_t *model, char *remapFile ){
 
 void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t** normals,
                                                         int numSTs, picoVec2_t **st, int numColors, picoColor_t **colors,
-                                                        picoShader_t* shader ){
+                                                        picoShader_t* shader, const char *name, picoIndex_t* smoothingGroup ){
        int i,j;
        int vertDataIndex;
        picoSurface_t* workSurface = NULL;
@@ -2011,8 +2258,10 @@ void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t**
        for ( i = 0 ; i < model->numSurfaces ; i++ )
        {
                workSurface = model->surface[i];
-               if ( workSurface->shader == shader ) {
-                       break;
+               if ( !name || !strcmp( workSurface->name, name ) ) {
+                       if ( workSurface->shader == shader ) {
+                               break;
+                       }
                }
        }
 
@@ -2027,7 +2276,7 @@ void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t**
 
                /* do surface setup */
                PicoSetSurfaceType( workSurface, PICO_TRIANGLES );
-               PicoSetSurfaceName( workSurface, shader->name );
+               PicoSetSurfaceName( workSurface, name ? name : shader->name );
                PicoSetSurfaceShader( workSurface, shader );
        }
 
@@ -2038,7 +2287,7 @@ void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t**
                int newVertIndex = PicoGetSurfaceNumIndexes( workSurface );
 
                /* get the index of the vertex that we're going to store at newVertIndex */
-               vertDataIndex = PicoFindSurfaceVertexNum( workSurface, *xyz[i], *normals[i], numSTs, st[i], numColors, colors[i] );
+               vertDataIndex = PicoFindSurfaceVertexNum( workSurface, *xyz[i], *normals[i], numSTs, st[i], numColors, colors[i], smoothingGroup[i] );
 
                /* the vertex wasn't found, so create a new vertex in the pool from the data we have */
                if ( vertDataIndex == -1 ) {
@@ -2058,6 +2307,8 @@ void PicoAddTriangleToModel( picoModel_t *model, picoVec3_t** xyz, picoVec3_t**
                        {
                                PicoSetSurfaceST( workSurface, j, vertDataIndex, st[i][j] );
                        }
+
+                       PicoSetSurfaceSmoothingGroup( workSurface, vertDataIndex, smoothingGroup[i] );
                }
 
                /* add this vertex to the triangle */