]> de.git.xonotic.org Git - xonotic/netradiant.git/blobdiff - libs/picomodel/lwo/surface.c
uncrustify! now the code is only ugly on the *inside*
[xonotic/netradiant.git] / libs / picomodel / lwo / surface.c
index 6456a957482ef042c414ceceaed5d45b6c24d9c0..07ce4d6275c2c36bef50217a820b24d19be7ab5e 100644 (file)
-/*\r
-======================================================================\r
-surface.c\r
-\r
-Surface functions for an LWO2 reader.\r
-\r
-Ernie Wright  17 Sep 00\r
-====================================================================== */\r
-\r
-#include "../picointernal.h"\r
-#include "lwo2.h"\r
-\r
-\r
-/*\r
-======================================================================\r
-lwFreePlugin()\r
-\r
-Free the memory used by an lwPlugin.\r
-====================================================================== */\r
-\r
-void lwFreePlugin( lwPlugin *p )\r
-{\r
-   if ( p ) {\r
-      if ( p->ord ) _pico_free( p->ord );\r
-      if ( p->name ) _pico_free( p->name );\r
-      if ( p->data ) _pico_free( p->data );\r
-      _pico_free( p );\r
-   }\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwFreeTexture()\r
-\r
-Free the memory used by an lwTexture.\r
-====================================================================== */\r
-\r
-void lwFreeTexture( lwTexture *t )\r
-{\r
-   if ( t ) {\r
-      if ( t->ord ) _pico_free( t->ord );\r
-      switch ( t->type ) {\r
-         case ID_IMAP:\r
-            if ( t->param.imap.vmap_name ) _pico_free( t->param.imap.vmap_name );\r
-            break;\r
-         case ID_PROC:\r
-            if ( t->param.proc.name ) _pico_free( t->param.proc.name );\r
-            if ( t->param.proc.data ) _pico_free( t->param.proc.data );\r
-            break;\r
-         case ID_GRAD:\r
-            if ( t->param.grad.key ) _pico_free( t->param.grad.key );\r
-            if ( t->param.grad.ikey ) _pico_free( t->param.grad.ikey );\r
-            break;\r
-      }\r
-      _pico_free( t );\r
-   }\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwFreeSurface()\r
-\r
-Free the memory used by an lwSurface.\r
-====================================================================== */\r
-\r
-void lwFreeSurface( lwSurface *surf )\r
-{\r
-   if ( surf ) {\r
-      if ( surf->name ) _pico_free( surf->name );\r
-      if ( surf->srcname ) _pico_free( surf->srcname );\r
-\r
-      lwListFree( surf->shader, lwFreePlugin );\r
-\r
-      lwListFree( surf->color.tex, lwFreeTexture );\r
-      lwListFree( surf->luminosity.tex, lwFreeTexture );\r
-      lwListFree( surf->diffuse.tex, lwFreeTexture );\r
-      lwListFree( surf->specularity.tex, lwFreeTexture );\r
-      lwListFree( surf->glossiness.tex, lwFreeTexture );\r
-      lwListFree( surf->reflection.val.tex, lwFreeTexture );\r
-      lwListFree( surf->transparency.val.tex, lwFreeTexture );\r
-      lwListFree( surf->eta.tex, lwFreeTexture );\r
-      lwListFree( surf->translucency.tex, lwFreeTexture );\r
-      lwListFree( surf->bump.tex, lwFreeTexture );\r
-\r
-      _pico_free( surf );\r
-   }\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwGetTHeader()\r
-\r
-Read a texture map header from a SURF.BLOK in an LWO2 file.  This is\r
-the first subchunk in a BLOK, and its contents are common to all three\r
-texture types.\r
-====================================================================== */\r
-\r
-int lwGetTHeader( picoMemStream_t *fp, int hsz, lwTexture *tex )\r
-{\r
-   unsigned int id;\r
-   unsigned short sz;\r
-   int pos, rlen;\r
-\r
-\r
-   /* remember where we started */\r
-\r
-   set_flen( 0 );\r
-   pos = _pico_memstream_tell( fp );\r
-\r
-   /* ordinal string */\r
-\r
-   tex->ord = getS0( fp );\r
-\r
-   /* first subchunk header */\r
-\r
-   id = getU4( fp );\r
-   sz = getU2( fp );\r
-   if ( 0 > get_flen() ) return 0;\r
-\r
-   /* process subchunks as they're encountered */\r
-\r
-   while ( 1 ) {\r
-      sz += sz & 1;\r
-      set_flen( 0 );\r
-\r
-      switch ( id ) {\r
-         case ID_CHAN:\r
-            tex->chan = getU4( fp );\r
-            break;\r
-\r
-         case ID_OPAC:\r
-            tex->opac_type = getU2( fp );\r
-            tex->opacity.val = getF4( fp );\r
-            tex->opacity.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_ENAB:\r
-            tex->enabled = getU2( fp );\r
-            break;\r
-\r
-         case ID_NEGA:\r
-            tex->negative = getU2( fp );\r
-            break;\r
-\r
-         case ID_AXIS:\r
-            tex->axis = getU2( fp );\r
-            break;\r
-\r
-         default:\r
-            break;\r
-      }\r
-\r
-      /* error while reading current subchunk? */\r
-\r
-      rlen = get_flen();\r
-      if ( rlen < 0 || rlen > sz ) return 0;\r
-\r
-      /* skip unread parts of the current subchunk */\r
-\r
-      if ( rlen < sz )\r
-         _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );\r
-\r
-      /* end of the texture header subchunk? */\r
-\r
-      if ( hsz <= _pico_memstream_tell( fp ) - pos )\r
-         break;\r
-\r
-      /* get the next subchunk header */\r
-\r
-      set_flen( 0 );\r
-      id = getU4( fp );\r
-      sz = getU2( fp );\r
-      if ( 6 != get_flen() ) return 0;\r
-   }\r
-\r
-   set_flen( _pico_memstream_tell( fp ) - pos );\r
-   return 1;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwGetTMap()\r
-\r
-Read a texture map from a SURF.BLOK in an LWO2 file.  The TMAP\r
-defines the mapping from texture to world or object coordinates.\r
-====================================================================== */\r
-\r
-int lwGetTMap( picoMemStream_t *fp, int tmapsz, lwTMap *tmap )\r
-{\r
-   unsigned int id;\r
-   unsigned short sz;\r
-   int rlen, pos, i;\r
-\r
-   pos = _pico_memstream_tell( fp );\r
-   id = getU4( fp );\r
-   sz = getU2( fp );\r
-   if ( 0 > get_flen() ) return 0;\r
-\r
-   while ( 1 ) {\r
-      sz += sz & 1;\r
-      set_flen( 0 );\r
-\r
-      switch ( id ) {\r
-         case ID_SIZE:\r
-            for ( i = 0; i < 3; i++ )\r
-               tmap->size.val[ i ] = getF4( fp );\r
-            tmap->size.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_CNTR:\r
-            for ( i = 0; i < 3; i++ )\r
-               tmap->center.val[ i ] = getF4( fp );\r
-            tmap->center.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_ROTA:\r
-            for ( i = 0; i < 3; i++ )\r
-               tmap->rotate.val[ i ] = getF4( fp );\r
-            tmap->rotate.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_FALL:\r
-            tmap->fall_type = getU2( fp );\r
-            for ( i = 0; i < 3; i++ )\r
-               tmap->falloff.val[ i ] = getF4( fp );\r
-            tmap->falloff.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_OREF:\r
-            tmap->ref_object = getS0( fp );\r
-            break;\r
-\r
-         case ID_CSYS:\r
-            tmap->coord_sys = getU2( fp );\r
-            break;\r
-\r
-         default:\r
-            break;\r
-      }\r
-\r
-      /* error while reading the current subchunk? */\r
-\r
-      rlen = get_flen();\r
-      if ( rlen < 0 || rlen > sz ) return 0;\r
-\r
-      /* skip unread parts of the current subchunk */\r
-\r
-      if ( rlen < sz )\r
-         _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );\r
-\r
-      /* end of the TMAP subchunk? */\r
-\r
-      if ( tmapsz <= _pico_memstream_tell( fp ) - pos )\r
-         break;\r
-\r
-      /* get the next subchunk header */\r
-\r
-      set_flen( 0 );\r
-      id = getU4( fp );\r
-      sz = getU2( fp );\r
-      if ( 6 != get_flen() ) return 0;\r
-   }\r
-\r
-   set_flen( _pico_memstream_tell( fp ) - pos );\r
-   return 1;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwGetImageMap()\r
-\r
-Read an lwImageMap from a SURF.BLOK in an LWO2 file.\r
-====================================================================== */\r
-\r
-int lwGetImageMap( picoMemStream_t *fp, int rsz, lwTexture *tex )\r
-{\r
-   unsigned int id;\r
-   unsigned short sz;\r
-   int rlen, pos;\r
-\r
-   pos = _pico_memstream_tell( fp );\r
-   id = getU4( fp );\r
-   sz = getU2( fp );\r
-   if ( 0 > get_flen() ) return 0;\r
-\r
-   while ( 1 ) {\r
-      sz += sz & 1;\r
-      set_flen( 0 );\r
-\r
-      switch ( id ) {\r
-         case ID_TMAP:\r
-            if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0;\r
-            break;\r
-\r
-         case ID_PROJ:\r
-            tex->param.imap.projection = getU2( fp );\r
-            break;\r
-\r
-         case ID_VMAP:\r
-            tex->param.imap.vmap_name = getS0( fp );\r
-            break;\r
-\r
-         case ID_AXIS:\r
-            tex->param.imap.axis = getU2( fp );\r
-            break;\r
-\r
-         case ID_IMAG:\r
-            tex->param.imap.cindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_WRAP:\r
-            tex->param.imap.wrapw_type = getU2( fp );\r
-            tex->param.imap.wraph_type = getU2( fp );\r
-            break;\r
-\r
-         case ID_WRPW:\r
-            tex->param.imap.wrapw.val = getF4( fp );\r
-            tex->param.imap.wrapw.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_WRPH:\r
-            tex->param.imap.wraph.val = getF4( fp );\r
-            tex->param.imap.wraph.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_AAST:\r
-            tex->param.imap.aas_flags = getU2( fp );\r
-            tex->param.imap.aa_strength = getF4( fp );\r
-            break;\r
-\r
-         case ID_PIXB:\r
-            tex->param.imap.pblend = getU2( fp );\r
-            break;\r
-\r
-         case ID_STCK:\r
-            tex->param.imap.stck.val = getF4( fp );\r
-            tex->param.imap.stck.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_TAMP:\r
-            tex->param.imap.amplitude.val = getF4( fp );\r
-            tex->param.imap.amplitude.eindex = getVX( fp );\r
-            break;\r
-\r
-         default:\r
-            break;\r
-      }\r
-\r
-      /* error while reading the current subchunk? */\r
-\r
-      rlen = get_flen();\r
-      if ( rlen < 0 || rlen > sz ) return 0;\r
-\r
-      /* skip unread parts of the current subchunk */\r
-\r
-      if ( rlen < sz )\r
-         _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );\r
-\r
-      /* end of the image map? */\r
-\r
-      if ( rsz <= _pico_memstream_tell( fp ) - pos )\r
-         break;\r
-\r
-      /* get the next subchunk header */\r
-\r
-      set_flen( 0 );\r
-      id = getU4( fp );\r
-      sz = getU2( fp );\r
-      if ( 6 != get_flen() ) return 0;\r
-   }\r
-\r
-   set_flen( _pico_memstream_tell( fp ) - pos );\r
-   return 1;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwGetProcedural()\r
-\r
-Read an lwProcedural from a SURF.BLOK in an LWO2 file.\r
-====================================================================== */\r
-\r
-int lwGetProcedural( picoMemStream_t *fp, int rsz, lwTexture *tex )\r
-{\r
-   unsigned int id;\r
-   unsigned short sz;\r
-   int rlen, pos;\r
-\r
-   pos = _pico_memstream_tell( fp );\r
-   id = getU4( fp );\r
-   sz = getU2( fp );\r
-   if ( 0 > get_flen() ) return 0;\r
-\r
-   while ( 1 ) {\r
-      sz += sz & 1;\r
-      set_flen( 0 );\r
-\r
-      switch ( id ) {\r
-         case ID_TMAP:\r
-            if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0;\r
-            break;\r
-\r
-         case ID_AXIS:\r
-            tex->param.proc.axis = getU2( fp );\r
-            break;\r
-\r
-         case ID_VALU:\r
-            tex->param.proc.value[ 0 ] = getF4( fp );\r
-            if ( sz >= 8 ) tex->param.proc.value[ 1 ] = getF4( fp );\r
-            if ( sz >= 12 ) tex->param.proc.value[ 2 ] = getF4( fp );\r
-            break;\r
-\r
-         case ID_FUNC:\r
-            tex->param.proc.name = getS0( fp );\r
-            rlen = get_flen();\r
-            tex->param.proc.data = getbytes( fp, sz - rlen );\r
-            break;\r
-\r
-         default:\r
-            break;\r
-      }\r
-\r
-      /* error while reading the current subchunk? */\r
-\r
-      rlen = get_flen();\r
-      if ( rlen < 0 || rlen > sz ) return 0;\r
-\r
-      /* skip unread parts of the current subchunk */\r
-\r
-      if ( rlen < sz )\r
-         _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );\r
-\r
-      /* end of the procedural block? */\r
-\r
-      if ( rsz <= _pico_memstream_tell( fp ) - pos )\r
-         break;\r
-\r
-      /* get the next subchunk header */\r
-\r
-      set_flen( 0 );\r
-      id = getU4( fp );\r
-      sz = getU2( fp );\r
-      if ( 6 != get_flen() ) return 0;\r
-   }\r
-\r
-   set_flen( _pico_memstream_tell( fp ) - pos );\r
-   return 1;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwGetGradient()\r
-\r
-Read an lwGradient from a SURF.BLOK in an LWO2 file.\r
-====================================================================== */\r
-\r
-int lwGetGradient( picoMemStream_t *fp, int rsz, lwTexture *tex )\r
-{\r
-   unsigned int id;\r
-   unsigned short sz;\r
-   int rlen, pos, i, j, nkeys;\r
-\r
-   pos = _pico_memstream_tell( fp );\r
-   id = getU4( fp );\r
-   sz = getU2( fp );\r
-   if ( 0 > get_flen() ) return 0;\r
-\r
-   while ( 1 ) {\r
-      sz += sz & 1;\r
-      set_flen( 0 );\r
-\r
-      switch ( id ) {\r
-         case ID_TMAP:\r
-            if ( !lwGetTMap( fp, sz, &tex->tmap )) return 0;\r
-            break;\r
-\r
-         case ID_PNAM:\r
-            tex->param.grad.paramname = getS0( fp );\r
-            break;\r
-\r
-         case ID_INAM:\r
-            tex->param.grad.itemname = getS0( fp );\r
-            break;\r
-\r
-         case ID_GRST:\r
-            tex->param.grad.start = getF4( fp );\r
-            break;\r
-\r
-         case ID_GREN:\r
-            tex->param.grad.end = getF4( fp );\r
-            break;\r
-\r
-         case ID_GRPT:\r
-            tex->param.grad.repeat = getU2( fp );\r
-            break;\r
-\r
-         case ID_FKEY:\r
-            nkeys = sz / sizeof( lwGradKey );\r
-            tex->param.grad.key = _pico_calloc( nkeys, sizeof( lwGradKey ));\r
-            if ( !tex->param.grad.key ) return 0;\r
-            for ( i = 0; i < nkeys; i++ ) {\r
-               tex->param.grad.key[ i ].value = getF4( fp );\r
-               for ( j = 0; j < 4; j++ )\r
-                  tex->param.grad.key[ i ].rgba[ j ] = getF4( fp );\r
-            }\r
-            break;\r
-\r
-         case ID_IKEY:\r
-            nkeys = sz / 2;\r
-            tex->param.grad.ikey = _pico_calloc( nkeys, sizeof( short ));\r
-            if ( !tex->param.grad.ikey ) return 0;\r
-            for ( i = 0; i < nkeys; i++ )\r
-               tex->param.grad.ikey[ i ] = getU2( fp );\r
-            break;\r
-\r
-         default:\r
-            break;\r
-      }\r
-\r
-      /* error while reading the current subchunk? */\r
-\r
-      rlen = get_flen();\r
-      if ( rlen < 0 || rlen > sz ) return 0;\r
-\r
-      /* skip unread parts of the current subchunk */\r
-\r
-      if ( rlen < sz )\r
-         _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );\r
-\r
-      /* end of the gradient? */\r
-\r
-      if ( rsz <= _pico_memstream_tell( fp ) - pos )\r
-         break;\r
-\r
-      /* get the next subchunk header */\r
-\r
-      set_flen( 0 );\r
-      id = getU4( fp );\r
-      sz = getU2( fp );\r
-      if ( 6 != get_flen() ) return 0;\r
-   }\r
-\r
-   set_flen( _pico_memstream_tell( fp ) - pos );\r
-   return 1;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwGetTexture()\r
-\r
-Read an lwTexture from a SURF.BLOK in an LWO2 file.\r
-====================================================================== */\r
-\r
-lwTexture *lwGetTexture( picoMemStream_t *fp, int bloksz, unsigned int type )\r
-{\r
-   lwTexture *tex;\r
-   unsigned short sz;\r
-   int ok;\r
-\r
-   tex = _pico_calloc( 1, sizeof( lwTexture ));\r
-   if ( !tex ) return NULL;\r
-\r
-   tex->type = type;\r
-   tex->tmap.size.val[ 0 ] =\r
-   tex->tmap.size.val[ 1 ] =\r
-   tex->tmap.size.val[ 2 ] = 1.0f;\r
-   tex->opacity.val = 1.0f;\r
-   tex->enabled = 1;\r
-\r
-   sz = getU2( fp );\r
-   if ( !lwGetTHeader( fp, sz, tex )) {\r
-      _pico_free( tex );\r
-      return NULL;\r
-   }\r
-\r
-   sz = bloksz - sz - 6;\r
-   switch ( type ) {\r
-      case ID_IMAP:  ok = lwGetImageMap( fp, sz, tex );  break;\r
-      case ID_PROC:  ok = lwGetProcedural( fp, sz, tex );  break;\r
-      case ID_GRAD:  ok = lwGetGradient( fp, sz, tex );  break;\r
-      default:\r
-         ok = !_pico_memstream_seek( fp, sz, PICO_SEEK_CUR );\r
-   }\r
-\r
-   if ( !ok ) {\r
-      lwFreeTexture( tex );\r
-      return NULL;\r
-   }\r
-\r
-   set_flen( bloksz );\r
-   return tex;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwGetShader()\r
-\r
-Read a shader record from a SURF.BLOK in an LWO2 file.\r
-====================================================================== */\r
-\r
-lwPlugin *lwGetShader( picoMemStream_t *fp, int bloksz )\r
-{\r
-   lwPlugin *shdr;\r
-   unsigned int id;\r
-   unsigned short sz;\r
-   int hsz, rlen, pos;\r
-\r
-   shdr = _pico_calloc( 1, sizeof( lwPlugin ));\r
-   if ( !shdr ) return NULL;\r
-\r
-   pos = _pico_memstream_tell( fp );\r
-   set_flen( 0 );\r
-   hsz = getU2( fp );\r
-   shdr->ord = getS0( fp );\r
-   id = getU4( fp );\r
-   sz = getU2( fp );\r
-   if ( 0 > get_flen() ) goto Fail;\r
-\r
-   while ( hsz > 0 ) {\r
-      sz += sz & 1;\r
-      hsz -= sz;\r
-      if ( id == ID_ENAB ) {\r
-         shdr->flags = getU2( fp );\r
-         break;\r
-      }\r
-      else {\r
-         _pico_memstream_seek( fp, sz, PICO_SEEK_CUR );\r
-         id = getU4( fp );\r
-         sz = getU2( fp );\r
-      }\r
-   }\r
-\r
-   id = getU4( fp );\r
-   sz = getU2( fp );\r
-   if ( 0 > get_flen() ) goto Fail;\r
-\r
-   while ( 1 ) {\r
-      sz += sz & 1;\r
-      set_flen( 0 );\r
-\r
-      switch ( id ) {\r
-         case ID_FUNC:\r
-            shdr->name = getS0( fp );\r
-            rlen = get_flen();\r
-            shdr->data = getbytes( fp, sz - rlen );\r
-            break;\r
-\r
-         default:\r
-            break;\r
-      }\r
-\r
-      /* error while reading the current subchunk? */\r
-\r
-      rlen = get_flen();\r
-      if ( rlen < 0 || rlen > sz ) goto Fail;\r
-\r
-      /* skip unread parts of the current subchunk */\r
-\r
-      if ( rlen < sz )\r
-         _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );\r
-\r
-      /* end of the shader block? */\r
-\r
-      if ( bloksz <= _pico_memstream_tell( fp ) - pos )\r
-         break;\r
-\r
-      /* get the next subchunk header */\r
-\r
-      set_flen( 0 );\r
-      id = getU4( fp );\r
-      sz = getU2( fp );\r
-      if ( 6 != get_flen() ) goto Fail;\r
-   }\r
-\r
-   set_flen( _pico_memstream_tell( fp ) - pos );\r
-   return shdr;\r
-\r
-Fail:\r
-   lwFreePlugin( shdr );\r
-   return NULL;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-compare_textures()\r
-compare_shaders()\r
-\r
-Callbacks for the lwListInsert() function, which is called to add\r
-textures to surface channels and shaders to surfaces.\r
-====================================================================== */\r
-\r
-static int compare_textures( lwTexture *a, lwTexture *b )\r
-{\r
-   return strcmp( a->ord, b->ord );\r
-}\r
-\r
-\r
-static int compare_shaders( lwPlugin *a, lwPlugin *b )\r
-{\r
-   return strcmp( a->ord, b->ord );\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-add_texture()\r
-\r
-Finds the surface channel (lwTParam or lwCParam) to which a texture is\r
-applied, then calls lwListInsert().\r
-====================================================================== */\r
-\r
-static int add_texture( lwSurface *surf, lwTexture *tex )\r
-{\r
-   lwTexture **list;\r
-\r
-   switch ( tex->chan ) {\r
-      case ID_COLR:  list = &surf->color.tex;             break;\r
-      case ID_LUMI:  list = &surf->luminosity.tex;        break;\r
-      case ID_DIFF:  list = &surf->diffuse.tex;           break;\r
-      case ID_SPEC:  list = &surf->specularity.tex;       break;\r
-      case ID_GLOS:  list = &surf->glossiness.tex;        break;\r
-      case ID_REFL:  list = &surf->reflection.val.tex;    break;\r
-      case ID_TRAN:  list = &surf->transparency.val.tex;  break;\r
-      case ID_RIND:  list = &surf->eta.tex;               break;\r
-      case ID_TRNL:  list = &surf->translucency.tex;      break;\r
-      case ID_BUMP:  list = &surf->bump.tex;              break;\r
-      default:  return 0;\r
-   }\r
-\r
-   lwListInsert( list, tex, compare_textures );\r
-   return 1;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwDefaultSurface()\r
-\r
-Allocate and initialize a surface.\r
-====================================================================== */\r
-\r
-lwSurface *lwDefaultSurface( void )\r
-{\r
-   lwSurface *surf;\r
-\r
-   surf = _pico_calloc( 1, sizeof( lwSurface ));\r
-   if ( !surf ) return NULL;\r
-\r
-   surf->color.rgb[ 0 ] = 0.78431f;\r
-   surf->color.rgb[ 1 ] = 0.78431f;\r
-   surf->color.rgb[ 2 ] = 0.78431f;\r
-   surf->diffuse.val    = 1.0f;\r
-   surf->glossiness.val = 0.4f;\r
-   surf->bump.val       = 1.0f;\r
-   surf->eta.val        = 1.0f;\r
-   surf->sideflags      = 1;\r
-\r
-   return surf;\r
-}\r
-\r
-\r
-/*\r
-======================================================================\r
-lwGetSurface()\r
-\r
-Read an lwSurface from an LWO2 file.\r
-====================================================================== */\r
-\r
-lwSurface *lwGetSurface( picoMemStream_t *fp, int cksize )\r
-{\r
-   lwSurface *surf;\r
-   lwTexture *tex;\r
-   lwPlugin *shdr;\r
-   unsigned int id, type;\r
-   unsigned short sz;\r
-   int pos, rlen;\r
-\r
-\r
-   /* allocate the Surface structure */\r
-\r
-   surf = _pico_calloc( 1, sizeof( lwSurface ));\r
-   if ( !surf ) goto Fail;\r
-\r
-   /* non-zero defaults */\r
-\r
-   surf->color.rgb[ 0 ] = 0.78431f;\r
-   surf->color.rgb[ 1 ] = 0.78431f;\r
-   surf->color.rgb[ 2 ] = 0.78431f;\r
-   surf->diffuse.val    = 1.0f;\r
-   surf->glossiness.val = 0.4f;\r
-   surf->bump.val       = 1.0f;\r
-   surf->eta.val        = 1.0f;\r
-   surf->sideflags      = 1;\r
-\r
-   /* remember where we started */\r
-\r
-   set_flen( 0 );\r
-   pos = _pico_memstream_tell( fp );\r
-\r
-   /* names */\r
-\r
-   surf->name = getS0( fp );\r
-   surf->srcname = getS0( fp );\r
-\r
-   /* first subchunk header */\r
-\r
-   id = getU4( fp );\r
-   sz = getU2( fp );\r
-   if ( 0 > get_flen() ) goto Fail;\r
-\r
-   /* process subchunks as they're encountered */\r
-\r
-   while ( 1 ) {\r
-      sz += sz & 1;\r
-      set_flen( 0 );\r
-\r
-      switch ( id ) {\r
-         case ID_COLR:\r
-            surf->color.rgb[ 0 ] = getF4( fp );\r
-            surf->color.rgb[ 1 ] = getF4( fp );\r
-            surf->color.rgb[ 2 ] = getF4( fp );\r
-            surf->color.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_LUMI:\r
-            surf->luminosity.val = getF4( fp );\r
-            surf->luminosity.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_DIFF:\r
-            surf->diffuse.val = getF4( fp );\r
-            surf->diffuse.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_SPEC:\r
-            surf->specularity.val = getF4( fp );\r
-            surf->specularity.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_GLOS:\r
-            surf->glossiness.val = getF4( fp );\r
-            surf->glossiness.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_REFL:\r
-            surf->reflection.val.val = getF4( fp );\r
-            surf->reflection.val.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_RFOP:\r
-            surf->reflection.options = getU2( fp );\r
-            break;\r
-\r
-         case ID_RIMG:\r
-            surf->reflection.cindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_RSAN:\r
-            surf->reflection.seam_angle = getF4( fp );\r
-            break;\r
-\r
-         case ID_TRAN:\r
-            surf->transparency.val.val = getF4( fp );\r
-            surf->transparency.val.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_TROP:\r
-            surf->transparency.options = getU2( fp );\r
-            break;\r
-\r
-         case ID_TIMG:\r
-            surf->transparency.cindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_RIND:\r
-            surf->eta.val = getF4( fp );\r
-            surf->eta.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_TRNL:\r
-            surf->translucency.val = getF4( fp );\r
-            surf->translucency.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_BUMP:\r
-            surf->bump.val = getF4( fp );\r
-            surf->bump.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_SMAN:\r
-            surf->smooth = getF4( fp );\r
-            break;\r
-\r
-         case ID_SIDE:\r
-            surf->sideflags = getU2( fp );\r
-            break;\r
-\r
-         case ID_CLRH:\r
-            surf->color_hilite.val = getF4( fp );\r
-            surf->color_hilite.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_CLRF:\r
-            surf->color_filter.val = getF4( fp );\r
-            surf->color_filter.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_ADTR:\r
-            surf->add_trans.val = getF4( fp );\r
-            surf->add_trans.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_SHRP:\r
-            surf->dif_sharp.val = getF4( fp );\r
-            surf->dif_sharp.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_GVAL:\r
-            surf->glow.val = getF4( fp );\r
-            surf->glow.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_LINE:\r
-            surf->line.enabled = 1;\r
-            if ( sz >= 2 ) surf->line.flags = getU2( fp );\r
-            if ( sz >= 6 ) surf->line.size.val = getF4( fp );\r
-            if ( sz >= 8 ) surf->line.size.eindex = getVX( fp );\r
-            break;\r
-\r
-         case ID_ALPH:\r
-            surf->alpha_mode = getU2( fp );\r
-            surf->alpha = getF4( fp );\r
-            break;\r
-\r
-         case ID_AVAL:\r
-            surf->alpha = getF4( fp );\r
-            break;\r
-\r
-         case ID_BLOK:\r
-            type = getU4( fp );\r
-\r
-            switch ( type ) {\r
-               case ID_IMAP:\r
-               case ID_PROC:\r
-               case ID_GRAD:\r
-                  tex = lwGetTexture( fp, sz - 4, type );\r
-                  if ( !tex ) goto Fail;\r
-                  if ( !add_texture( surf, tex ))\r
-                     lwFreeTexture( tex );\r
-                  set_flen( 4 + get_flen() );\r
-                  break;\r
-               case ID_SHDR:\r
-                  shdr = lwGetShader( fp, sz - 4 );\r
-                  if ( !shdr ) goto Fail;\r
-                  lwListInsert( &surf->shader, shdr, compare_shaders );\r
-                  ++surf->nshaders;\r
-                  set_flen( 4 + get_flen() );\r
-                  break;\r
-            }\r
-            break;\r
-\r
-         default:\r
-            break;\r
-      }\r
-\r
-      /* error while reading current subchunk? */\r
-\r
-      rlen = get_flen();\r
-      if ( rlen < 0 || rlen > sz ) goto Fail;\r
-\r
-      /* skip unread parts of the current subchunk */\r
-\r
-      if ( rlen < sz )\r
-         _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );\r
-\r
-      /* end of the SURF chunk? */\r
-\r
-      if ( cksize <= _pico_memstream_tell( fp ) - pos )\r
-         break;\r
-\r
-      /* get the next subchunk header */\r
-\r
-      set_flen( 0 );\r
-      id = getU4( fp );\r
-      sz = getU2( fp );\r
-      if ( 6 != get_flen() ) goto Fail;\r
-   }\r
-\r
-   return surf;\r
-\r
-Fail:\r
-   if ( surf ) lwFreeSurface( surf );\r
-   return NULL;\r
-}\r
+/*
+   ======================================================================
+   surface.c
+
+   Surface functions for an LWO2 reader.
+
+   Ernie Wright  17 Sep 00
+   ====================================================================== */
+
+#include "../picointernal.h"
+#include "lwo2.h"
+
+
+/*
+   ======================================================================
+   lwFreePlugin()
+
+   Free the memory used by an lwPlugin.
+   ====================================================================== */
+
+void lwFreePlugin( lwPlugin *p ){
+       if ( p ) {
+               if ( p->ord ) {
+                       _pico_free( p->ord );
+               }
+               if ( p->name ) {
+                       _pico_free( p->name );
+               }
+               if ( p->data ) {
+                       _pico_free( p->data );
+               }
+               _pico_free( p );
+       }
+}
+
+
+/*
+   ======================================================================
+   lwFreeTexture()
+
+   Free the memory used by an lwTexture.
+   ====================================================================== */
+
+void lwFreeTexture( lwTexture *t ){
+       if ( t ) {
+               if ( t->ord ) {
+                       _pico_free( t->ord );
+               }
+               switch ( t->type ) {
+               case ID_IMAP:
+                       if ( t->param.imap.vmap_name ) {
+                               _pico_free( t->param.imap.vmap_name );
+                       }
+                       break;
+               case ID_PROC:
+                       if ( t->param.proc.name ) {
+                               _pico_free( t->param.proc.name );
+                       }
+                       if ( t->param.proc.data ) {
+                               _pico_free( t->param.proc.data );
+                       }
+                       break;
+               case ID_GRAD:
+                       if ( t->param.grad.key ) {
+                               _pico_free( t->param.grad.key );
+                       }
+                       if ( t->param.grad.ikey ) {
+                               _pico_free( t->param.grad.ikey );
+                       }
+                       break;
+               }
+               _pico_free( t );
+       }
+}
+
+
+/*
+   ======================================================================
+   lwFreeSurface()
+
+   Free the memory used by an lwSurface.
+   ====================================================================== */
+
+void lwFreeSurface( lwSurface *surf ){
+       if ( surf ) {
+               if ( surf->name ) {
+                       _pico_free( surf->name );
+               }
+               if ( surf->srcname ) {
+                       _pico_free( surf->srcname );
+               }
+
+               lwListFree( surf->shader, lwFreePlugin );
+
+               lwListFree( surf->color.tex, lwFreeTexture );
+               lwListFree( surf->luminosity.tex, lwFreeTexture );
+               lwListFree( surf->diffuse.tex, lwFreeTexture );
+               lwListFree( surf->specularity.tex, lwFreeTexture );
+               lwListFree( surf->glossiness.tex, lwFreeTexture );
+               lwListFree( surf->reflection.val.tex, lwFreeTexture );
+               lwListFree( surf->transparency.val.tex, lwFreeTexture );
+               lwListFree( surf->eta.tex, lwFreeTexture );
+               lwListFree( surf->translucency.tex, lwFreeTexture );
+               lwListFree( surf->bump.tex, lwFreeTexture );
+
+               _pico_free( surf );
+       }
+}
+
+
+/*
+   ======================================================================
+   lwGetTHeader()
+
+   Read a texture map header from a SURF.BLOK in an LWO2 file.  This is
+   the first subchunk in a BLOK, and its contents are common to all three
+   texture types.
+   ====================================================================== */
+
+int lwGetTHeader( picoMemStream_t *fp, int hsz, lwTexture *tex ){
+       unsigned int id;
+       unsigned short sz;
+       int pos, rlen;
+
+
+       /* remember where we started */
+
+       set_flen( 0 );
+       pos = _pico_memstream_tell( fp );
+
+       /* ordinal string */
+
+       tex->ord = getS0( fp );
+
+       /* first subchunk header */
+
+       id = getU4( fp );
+       sz = getU2( fp );
+       if ( 0 > get_flen() ) {
+               return 0;
+       }
+
+       /* process subchunks as they're encountered */
+
+       while ( 1 ) {
+               sz += sz & 1;
+               set_flen( 0 );
+
+               switch ( id ) {
+               case ID_CHAN:
+                       tex->chan = getU4( fp );
+                       break;
+
+               case ID_OPAC:
+                       tex->opac_type = getU2( fp );
+                       tex->opacity.val = getF4( fp );
+                       tex->opacity.eindex = getVX( fp );
+                       break;
+
+               case ID_ENAB:
+                       tex->enabled = getU2( fp );
+                       break;
+
+               case ID_NEGA:
+                       tex->negative = getU2( fp );
+                       break;
+
+               case ID_AXIS:
+                       tex->axis = getU2( fp );
+                       break;
+
+               default:
+                       break;
+               }
+
+               /* error while reading current subchunk? */
+
+               rlen = get_flen();
+               if ( rlen < 0 || rlen > sz ) {
+                       return 0;
+               }
+
+               /* skip unread parts of the current subchunk */
+
+               if ( rlen < sz ) {
+                       _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );
+               }
+
+               /* end of the texture header subchunk? */
+
+               if ( hsz <= _pico_memstream_tell( fp ) - pos ) {
+                       break;
+               }
+
+               /* get the next subchunk header */
+
+               set_flen( 0 );
+               id = getU4( fp );
+               sz = getU2( fp );
+               if ( 6 != get_flen() ) {
+                       return 0;
+               }
+       }
+
+       set_flen( _pico_memstream_tell( fp ) - pos );
+       return 1;
+}
+
+
+/*
+   ======================================================================
+   lwGetTMap()
+
+   Read a texture map from a SURF.BLOK in an LWO2 file.  The TMAP
+   defines the mapping from texture to world or object coordinates.
+   ====================================================================== */
+
+int lwGetTMap( picoMemStream_t *fp, int tmapsz, lwTMap *tmap ){
+       unsigned int id;
+       unsigned short sz;
+       int rlen, pos, i;
+
+       pos = _pico_memstream_tell( fp );
+       id = getU4( fp );
+       sz = getU2( fp );
+       if ( 0 > get_flen() ) {
+               return 0;
+       }
+
+       while ( 1 ) {
+               sz += sz & 1;
+               set_flen( 0 );
+
+               switch ( id ) {
+               case ID_SIZE:
+                       for ( i = 0; i < 3; i++ )
+                               tmap->size.val[ i ] = getF4( fp );
+                       tmap->size.eindex = getVX( fp );
+                       break;
+
+               case ID_CNTR:
+                       for ( i = 0; i < 3; i++ )
+                               tmap->center.val[ i ] = getF4( fp );
+                       tmap->center.eindex = getVX( fp );
+                       break;
+
+               case ID_ROTA:
+                       for ( i = 0; i < 3; i++ )
+                               tmap->rotate.val[ i ] = getF4( fp );
+                       tmap->rotate.eindex = getVX( fp );
+                       break;
+
+               case ID_FALL:
+                       tmap->fall_type = getU2( fp );
+                       for ( i = 0; i < 3; i++ )
+                               tmap->falloff.val[ i ] = getF4( fp );
+                       tmap->falloff.eindex = getVX( fp );
+                       break;
+
+               case ID_OREF:
+                       tmap->ref_object = getS0( fp );
+                       break;
+
+               case ID_CSYS:
+                       tmap->coord_sys = getU2( fp );
+                       break;
+
+               default:
+                       break;
+               }
+
+               /* error while reading the current subchunk? */
+
+               rlen = get_flen();
+               if ( rlen < 0 || rlen > sz ) {
+                       return 0;
+               }
+
+               /* skip unread parts of the current subchunk */
+
+               if ( rlen < sz ) {
+                       _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );
+               }
+
+               /* end of the TMAP subchunk? */
+
+               if ( tmapsz <= _pico_memstream_tell( fp ) - pos ) {
+                       break;
+               }
+
+               /* get the next subchunk header */
+
+               set_flen( 0 );
+               id = getU4( fp );
+               sz = getU2( fp );
+               if ( 6 != get_flen() ) {
+                       return 0;
+               }
+       }
+
+       set_flen( _pico_memstream_tell( fp ) - pos );
+       return 1;
+}
+
+
+/*
+   ======================================================================
+   lwGetImageMap()
+
+   Read an lwImageMap from a SURF.BLOK in an LWO2 file.
+   ====================================================================== */
+
+int lwGetImageMap( picoMemStream_t *fp, int rsz, lwTexture *tex ){
+       unsigned int id;
+       unsigned short sz;
+       int rlen, pos;
+
+       pos = _pico_memstream_tell( fp );
+       id = getU4( fp );
+       sz = getU2( fp );
+       if ( 0 > get_flen() ) {
+               return 0;
+       }
+
+       while ( 1 ) {
+               sz += sz & 1;
+               set_flen( 0 );
+
+               switch ( id ) {
+               case ID_TMAP:
+                       if ( !lwGetTMap( fp, sz, &tex->tmap ) ) {
+                               return 0;
+                       }
+                       break;
+
+               case ID_PROJ:
+                       tex->param.imap.projection = getU2( fp );
+                       break;
+
+               case ID_VMAP:
+                       tex->param.imap.vmap_name = getS0( fp );
+                       break;
+
+               case ID_AXIS:
+                       tex->param.imap.axis = getU2( fp );
+                       break;
+
+               case ID_IMAG:
+                       tex->param.imap.cindex = getVX( fp );
+                       break;
+
+               case ID_WRAP:
+                       tex->param.imap.wrapw_type = getU2( fp );
+                       tex->param.imap.wraph_type = getU2( fp );
+                       break;
+
+               case ID_WRPW:
+                       tex->param.imap.wrapw.val = getF4( fp );
+                       tex->param.imap.wrapw.eindex = getVX( fp );
+                       break;
+
+               case ID_WRPH:
+                       tex->param.imap.wraph.val = getF4( fp );
+                       tex->param.imap.wraph.eindex = getVX( fp );
+                       break;
+
+               case ID_AAST:
+                       tex->param.imap.aas_flags = getU2( fp );
+                       tex->param.imap.aa_strength = getF4( fp );
+                       break;
+
+               case ID_PIXB:
+                       tex->param.imap.pblend = getU2( fp );
+                       break;
+
+               case ID_STCK:
+                       tex->param.imap.stck.val = getF4( fp );
+                       tex->param.imap.stck.eindex = getVX( fp );
+                       break;
+
+               case ID_TAMP:
+                       tex->param.imap.amplitude.val = getF4( fp );
+                       tex->param.imap.amplitude.eindex = getVX( fp );
+                       break;
+
+               default:
+                       break;
+               }
+
+               /* error while reading the current subchunk? */
+
+               rlen = get_flen();
+               if ( rlen < 0 || rlen > sz ) {
+                       return 0;
+               }
+
+               /* skip unread parts of the current subchunk */
+
+               if ( rlen < sz ) {
+                       _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );
+               }
+
+               /* end of the image map? */
+
+               if ( rsz <= _pico_memstream_tell( fp ) - pos ) {
+                       break;
+               }
+
+               /* get the next subchunk header */
+
+               set_flen( 0 );
+               id = getU4( fp );
+               sz = getU2( fp );
+               if ( 6 != get_flen() ) {
+                       return 0;
+               }
+       }
+
+       set_flen( _pico_memstream_tell( fp ) - pos );
+       return 1;
+}
+
+
+/*
+   ======================================================================
+   lwGetProcedural()
+
+   Read an lwProcedural from a SURF.BLOK in an LWO2 file.
+   ====================================================================== */
+
+int lwGetProcedural( picoMemStream_t *fp, int rsz, lwTexture *tex ){
+       unsigned int id;
+       unsigned short sz;
+       int rlen, pos;
+
+       pos = _pico_memstream_tell( fp );
+       id = getU4( fp );
+       sz = getU2( fp );
+       if ( 0 > get_flen() ) {
+               return 0;
+       }
+
+       while ( 1 ) {
+               sz += sz & 1;
+               set_flen( 0 );
+
+               switch ( id ) {
+               case ID_TMAP:
+                       if ( !lwGetTMap( fp, sz, &tex->tmap ) ) {
+                               return 0;
+                       }
+                       break;
+
+               case ID_AXIS:
+                       tex->param.proc.axis = getU2( fp );
+                       break;
+
+               case ID_VALU:
+                       tex->param.proc.value[ 0 ] = getF4( fp );
+                       if ( sz >= 8 ) {
+                               tex->param.proc.value[ 1 ] = getF4( fp );
+                       }
+                       if ( sz >= 12 ) {
+                               tex->param.proc.value[ 2 ] = getF4( fp );
+                       }
+                       break;
+
+               case ID_FUNC:
+                       tex->param.proc.name = getS0( fp );
+                       rlen = get_flen();
+                       tex->param.proc.data = getbytes( fp, sz - rlen );
+                       break;
+
+               default:
+                       break;
+               }
+
+               /* error while reading the current subchunk? */
+
+               rlen = get_flen();
+               if ( rlen < 0 || rlen > sz ) {
+                       return 0;
+               }
+
+               /* skip unread parts of the current subchunk */
+
+               if ( rlen < sz ) {
+                       _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );
+               }
+
+               /* end of the procedural block? */
+
+               if ( rsz <= _pico_memstream_tell( fp ) - pos ) {
+                       break;
+               }
+
+               /* get the next subchunk header */
+
+               set_flen( 0 );
+               id = getU4( fp );
+               sz = getU2( fp );
+               if ( 6 != get_flen() ) {
+                       return 0;
+               }
+       }
+
+       set_flen( _pico_memstream_tell( fp ) - pos );
+       return 1;
+}
+
+
+/*
+   ======================================================================
+   lwGetGradient()
+
+   Read an lwGradient from a SURF.BLOK in an LWO2 file.
+   ====================================================================== */
+
+int lwGetGradient( picoMemStream_t *fp, int rsz, lwTexture *tex ){
+       unsigned int id;
+       unsigned short sz;
+       int rlen, pos, i, j, nkeys;
+
+       pos = _pico_memstream_tell( fp );
+       id = getU4( fp );
+       sz = getU2( fp );
+       if ( 0 > get_flen() ) {
+               return 0;
+       }
+
+       while ( 1 ) {
+               sz += sz & 1;
+               set_flen( 0 );
+
+               switch ( id ) {
+               case ID_TMAP:
+                       if ( !lwGetTMap( fp, sz, &tex->tmap ) ) {
+                               return 0;
+                       }
+                       break;
+
+               case ID_PNAM:
+                       tex->param.grad.paramname = getS0( fp );
+                       break;
+
+               case ID_INAM:
+                       tex->param.grad.itemname = getS0( fp );
+                       break;
+
+               case ID_GRST:
+                       tex->param.grad.start = getF4( fp );
+                       break;
+
+               case ID_GREN:
+                       tex->param.grad.end = getF4( fp );
+                       break;
+
+               case ID_GRPT:
+                       tex->param.grad.repeat = getU2( fp );
+                       break;
+
+               case ID_FKEY:
+                       nkeys = sz / sizeof( lwGradKey );
+                       tex->param.grad.key = _pico_calloc( nkeys, sizeof( lwGradKey ) );
+                       if ( !tex->param.grad.key ) {
+                               return 0;
+                       }
+                       for ( i = 0; i < nkeys; i++ ) {
+                               tex->param.grad.key[ i ].value = getF4( fp );
+                               for ( j = 0; j < 4; j++ )
+                                       tex->param.grad.key[ i ].rgba[ j ] = getF4( fp );
+                       }
+                       break;
+
+               case ID_IKEY:
+                       nkeys = sz / 2;
+                       tex->param.grad.ikey = _pico_calloc( nkeys, sizeof( short ) );
+                       if ( !tex->param.grad.ikey ) {
+                               return 0;
+                       }
+                       for ( i = 0; i < nkeys; i++ )
+                               tex->param.grad.ikey[ i ] = getU2( fp );
+                       break;
+
+               default:
+                       break;
+               }
+
+               /* error while reading the current subchunk? */
+
+               rlen = get_flen();
+               if ( rlen < 0 || rlen > sz ) {
+                       return 0;
+               }
+
+               /* skip unread parts of the current subchunk */
+
+               if ( rlen < sz ) {
+                       _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );
+               }
+
+               /* end of the gradient? */
+
+               if ( rsz <= _pico_memstream_tell( fp ) - pos ) {
+                       break;
+               }
+
+               /* get the next subchunk header */
+
+               set_flen( 0 );
+               id = getU4( fp );
+               sz = getU2( fp );
+               if ( 6 != get_flen() ) {
+                       return 0;
+               }
+       }
+
+       set_flen( _pico_memstream_tell( fp ) - pos );
+       return 1;
+}
+
+
+/*
+   ======================================================================
+   lwGetTexture()
+
+   Read an lwTexture from a SURF.BLOK in an LWO2 file.
+   ====================================================================== */
+
+lwTexture *lwGetTexture( picoMemStream_t *fp, int bloksz, unsigned int type ){
+       lwTexture *tex;
+       unsigned short sz;
+       int ok;
+
+       tex = _pico_calloc( 1, sizeof( lwTexture ) );
+       if ( !tex ) {
+               return NULL;
+       }
+
+       tex->type = type;
+       tex->tmap.size.val[ 0 ] =
+               tex->tmap.size.val[ 1 ] =
+                       tex->tmap.size.val[ 2 ] = 1.0f;
+       tex->opacity.val = 1.0f;
+       tex->enabled = 1;
+
+       sz = getU2( fp );
+       if ( !lwGetTHeader( fp, sz, tex ) ) {
+               _pico_free( tex );
+               return NULL;
+       }
+
+       sz = bloksz - sz - 6;
+       switch ( type ) {
+       case ID_IMAP:  ok = lwGetImageMap( fp, sz, tex );  break;
+       case ID_PROC:  ok = lwGetProcedural( fp, sz, tex );  break;
+       case ID_GRAD:  ok = lwGetGradient( fp, sz, tex );  break;
+       default:
+               ok = !_pico_memstream_seek( fp, sz, PICO_SEEK_CUR );
+       }
+
+       if ( !ok ) {
+               lwFreeTexture( tex );
+               return NULL;
+       }
+
+       set_flen( bloksz );
+       return tex;
+}
+
+
+/*
+   ======================================================================
+   lwGetShader()
+
+   Read a shader record from a SURF.BLOK in an LWO2 file.
+   ====================================================================== */
+
+lwPlugin *lwGetShader( picoMemStream_t *fp, int bloksz ){
+       lwPlugin *shdr;
+       unsigned int id;
+       unsigned short sz;
+       int hsz, rlen, pos;
+
+       shdr = _pico_calloc( 1, sizeof( lwPlugin ) );
+       if ( !shdr ) {
+               return NULL;
+       }
+
+       pos = _pico_memstream_tell( fp );
+       set_flen( 0 );
+       hsz = getU2( fp );
+       shdr->ord = getS0( fp );
+       id = getU4( fp );
+       sz = getU2( fp );
+       if ( 0 > get_flen() ) {
+               goto Fail;
+       }
+
+       while ( hsz > 0 ) {
+               sz += sz & 1;
+               hsz -= sz;
+               if ( id == ID_ENAB ) {
+                       shdr->flags = getU2( fp );
+                       break;
+               }
+               else {
+                       _pico_memstream_seek( fp, sz, PICO_SEEK_CUR );
+                       id = getU4( fp );
+                       sz = getU2( fp );
+               }
+       }
+
+       id = getU4( fp );
+       sz = getU2( fp );
+       if ( 0 > get_flen() ) {
+               goto Fail;
+       }
+
+       while ( 1 ) {
+               sz += sz & 1;
+               set_flen( 0 );
+
+               switch ( id ) {
+               case ID_FUNC:
+                       shdr->name = getS0( fp );
+                       rlen = get_flen();
+                       shdr->data = getbytes( fp, sz - rlen );
+                       break;
+
+               default:
+                       break;
+               }
+
+               /* error while reading the current subchunk? */
+
+               rlen = get_flen();
+               if ( rlen < 0 || rlen > sz ) {
+                       goto Fail;
+               }
+
+               /* skip unread parts of the current subchunk */
+
+               if ( rlen < sz ) {
+                       _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );
+               }
+
+               /* end of the shader block? */
+
+               if ( bloksz <= _pico_memstream_tell( fp ) - pos ) {
+                       break;
+               }
+
+               /* get the next subchunk header */
+
+               set_flen( 0 );
+               id = getU4( fp );
+               sz = getU2( fp );
+               if ( 6 != get_flen() ) {
+                       goto Fail;
+               }
+       }
+
+       set_flen( _pico_memstream_tell( fp ) - pos );
+       return shdr;
+
+Fail:
+       lwFreePlugin( shdr );
+       return NULL;
+}
+
+
+/*
+   ======================================================================
+   compare_textures()
+   compare_shaders()
+
+   Callbacks for the lwListInsert() function, which is called to add
+   textures to surface channels and shaders to surfaces.
+   ====================================================================== */
+
+static int compare_textures( lwTexture *a, lwTexture *b ){
+       return strcmp( a->ord, b->ord );
+}
+
+
+static int compare_shaders( lwPlugin *a, lwPlugin *b ){
+       return strcmp( a->ord, b->ord );
+}
+
+
+/*
+   ======================================================================
+   add_texture()
+
+   Finds the surface channel (lwTParam or lwCParam) to which a texture is
+   applied, then calls lwListInsert().
+   ====================================================================== */
+
+static int add_texture( lwSurface *surf, lwTexture *tex ){
+       lwTexture **list;
+
+       switch ( tex->chan ) {
+       case ID_COLR:  list = &surf->color.tex;             break;
+       case ID_LUMI:  list = &surf->luminosity.tex;        break;
+       case ID_DIFF:  list = &surf->diffuse.tex;           break;
+       case ID_SPEC:  list = &surf->specularity.tex;       break;
+       case ID_GLOS:  list = &surf->glossiness.tex;        break;
+       case ID_REFL:  list = &surf->reflection.val.tex;    break;
+       case ID_TRAN:  list = &surf->transparency.val.tex;  break;
+       case ID_RIND:  list = &surf->eta.tex;               break;
+       case ID_TRNL:  list = &surf->translucency.tex;      break;
+       case ID_BUMP:  list = &surf->bump.tex;              break;
+       default:  return 0;
+       }
+
+       lwListInsert( list, tex, compare_textures );
+       return 1;
+}
+
+
+/*
+   ======================================================================
+   lwDefaultSurface()
+
+   Allocate and initialize a surface.
+   ====================================================================== */
+
+lwSurface *lwDefaultSurface( void ){
+       lwSurface *surf;
+
+       surf = _pico_calloc( 1, sizeof( lwSurface ) );
+       if ( !surf ) {
+               return NULL;
+       }
+
+       surf->color.rgb[ 0 ] = 0.78431f;
+       surf->color.rgb[ 1 ] = 0.78431f;
+       surf->color.rgb[ 2 ] = 0.78431f;
+       surf->diffuse.val    = 1.0f;
+       surf->glossiness.val = 0.4f;
+       surf->bump.val       = 1.0f;
+       surf->eta.val        = 1.0f;
+       surf->sideflags      = 1;
+
+       return surf;
+}
+
+
+/*
+   ======================================================================
+   lwGetSurface()
+
+   Read an lwSurface from an LWO2 file.
+   ====================================================================== */
+
+lwSurface *lwGetSurface( picoMemStream_t *fp, int cksize ){
+       lwSurface *surf;
+       lwTexture *tex;
+       lwPlugin *shdr;
+       unsigned int id, type;
+       unsigned short sz;
+       int pos, rlen;
+
+
+       /* allocate the Surface structure */
+
+       surf = _pico_calloc( 1, sizeof( lwSurface ) );
+       if ( !surf ) {
+               goto Fail;
+       }
+
+       /* non-zero defaults */
+
+       surf->color.rgb[ 0 ] = 0.78431f;
+       surf->color.rgb[ 1 ] = 0.78431f;
+       surf->color.rgb[ 2 ] = 0.78431f;
+       surf->diffuse.val    = 1.0f;
+       surf->glossiness.val = 0.4f;
+       surf->bump.val       = 1.0f;
+       surf->eta.val        = 1.0f;
+       surf->sideflags      = 1;
+
+       /* remember where we started */
+
+       set_flen( 0 );
+       pos = _pico_memstream_tell( fp );
+
+       /* names */
+
+       surf->name = getS0( fp );
+       surf->srcname = getS0( fp );
+
+       /* first subchunk header */
+
+       id = getU4( fp );
+       sz = getU2( fp );
+       if ( 0 > get_flen() ) {
+               goto Fail;
+       }
+
+       /* process subchunks as they're encountered */
+
+       while ( 1 ) {
+               sz += sz & 1;
+               set_flen( 0 );
+
+               switch ( id ) {
+               case ID_COLR:
+                       surf->color.rgb[ 0 ] = getF4( fp );
+                       surf->color.rgb[ 1 ] = getF4( fp );
+                       surf->color.rgb[ 2 ] = getF4( fp );
+                       surf->color.eindex = getVX( fp );
+                       break;
+
+               case ID_LUMI:
+                       surf->luminosity.val = getF4( fp );
+                       surf->luminosity.eindex = getVX( fp );
+                       break;
+
+               case ID_DIFF:
+                       surf->diffuse.val = getF4( fp );
+                       surf->diffuse.eindex = getVX( fp );
+                       break;
+
+               case ID_SPEC:
+                       surf->specularity.val = getF4( fp );
+                       surf->specularity.eindex = getVX( fp );
+                       break;
+
+               case ID_GLOS:
+                       surf->glossiness.val = getF4( fp );
+                       surf->glossiness.eindex = getVX( fp );
+                       break;
+
+               case ID_REFL:
+                       surf->reflection.val.val = getF4( fp );
+                       surf->reflection.val.eindex = getVX( fp );
+                       break;
+
+               case ID_RFOP:
+                       surf->reflection.options = getU2( fp );
+                       break;
+
+               case ID_RIMG:
+                       surf->reflection.cindex = getVX( fp );
+                       break;
+
+               case ID_RSAN:
+                       surf->reflection.seam_angle = getF4( fp );
+                       break;
+
+               case ID_TRAN:
+                       surf->transparency.val.val = getF4( fp );
+                       surf->transparency.val.eindex = getVX( fp );
+                       break;
+
+               case ID_TROP:
+                       surf->transparency.options = getU2( fp );
+                       break;
+
+               case ID_TIMG:
+                       surf->transparency.cindex = getVX( fp );
+                       break;
+
+               case ID_RIND:
+                       surf->eta.val = getF4( fp );
+                       surf->eta.eindex = getVX( fp );
+                       break;
+
+               case ID_TRNL:
+                       surf->translucency.val = getF4( fp );
+                       surf->translucency.eindex = getVX( fp );
+                       break;
+
+               case ID_BUMP:
+                       surf->bump.val = getF4( fp );
+                       surf->bump.eindex = getVX( fp );
+                       break;
+
+               case ID_SMAN:
+                       surf->smooth = getF4( fp );
+                       break;
+
+               case ID_SIDE:
+                       surf->sideflags = getU2( fp );
+                       break;
+
+               case ID_CLRH:
+                       surf->color_hilite.val = getF4( fp );
+                       surf->color_hilite.eindex = getVX( fp );
+                       break;
+
+               case ID_CLRF:
+                       surf->color_filter.val = getF4( fp );
+                       surf->color_filter.eindex = getVX( fp );
+                       break;
+
+               case ID_ADTR:
+                       surf->add_trans.val = getF4( fp );
+                       surf->add_trans.eindex = getVX( fp );
+                       break;
+
+               case ID_SHRP:
+                       surf->dif_sharp.val = getF4( fp );
+                       surf->dif_sharp.eindex = getVX( fp );
+                       break;
+
+               case ID_GVAL:
+                       surf->glow.val = getF4( fp );
+                       surf->glow.eindex = getVX( fp );
+                       break;
+
+               case ID_LINE:
+                       surf->line.enabled = 1;
+                       if ( sz >= 2 ) {
+                               surf->line.flags = getU2( fp );
+                       }
+                       if ( sz >= 6 ) {
+                               surf->line.size.val = getF4( fp );
+                       }
+                       if ( sz >= 8 ) {
+                               surf->line.size.eindex = getVX( fp );
+                       }
+                       break;
+
+               case ID_ALPH:
+                       surf->alpha_mode = getU2( fp );
+                       surf->alpha = getF4( fp );
+                       break;
+
+               case ID_AVAL:
+                       surf->alpha = getF4( fp );
+                       break;
+
+               case ID_BLOK:
+                       type = getU4( fp );
+
+                       switch ( type ) {
+                       case ID_IMAP:
+                       case ID_PROC:
+                       case ID_GRAD:
+                               tex = lwGetTexture( fp, sz - 4, type );
+                               if ( !tex ) {
+                                       goto Fail;
+                               }
+                               if ( !add_texture( surf, tex ) ) {
+                                       lwFreeTexture( tex );
+                               }
+                               set_flen( 4 + get_flen() );
+                               break;
+                       case ID_SHDR:
+                               shdr = lwGetShader( fp, sz - 4 );
+                               if ( !shdr ) {
+                                       goto Fail;
+                               }
+                               lwListInsert( &surf->shader, shdr, compare_shaders );
+                               ++surf->nshaders;
+                               set_flen( 4 + get_flen() );
+                               break;
+                       }
+                       break;
+
+               default:
+                       break;
+               }
+
+               /* error while reading current subchunk? */
+
+               rlen = get_flen();
+               if ( rlen < 0 || rlen > sz ) {
+                       goto Fail;
+               }
+
+               /* skip unread parts of the current subchunk */
+
+               if ( rlen < sz ) {
+                       _pico_memstream_seek( fp, sz - rlen, PICO_SEEK_CUR );
+               }
+
+               /* end of the SURF chunk? */
+
+               if ( cksize <= _pico_memstream_tell( fp ) - pos ) {
+                       break;
+               }
+
+               /* get the next subchunk header */
+
+               set_flen( 0 );
+               id = getU4( fp );
+               sz = getU2( fp );
+               if ( 6 != get_flen() ) {
+                       goto Fail;
+               }
+       }
+
+       return surf;
+
+Fail:
+       if ( surf ) {
+               lwFreeSurface( surf );
+       }
+       return NULL;
+}