]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - tools/quake3/q3data/3dslib.c
eol style
[xonotic/netradiant.git] / tools / quake3 / q3data / 3dslib.c
1 #include <assert.h>
2 #include "q3data.h"
3
4 static void Load3DS( const char *filename, _3DS_t *p3DS, qboolean verbose );
5
6 static qboolean s_verbose;
7
8 #define MAX_MATERIALS 100
9 #define MAX_NAMED_OBJECTS 100
10 #define MAX_MESH_MATERIAL_GROUPS 100
11 #define MAX_TRI_OBJECTS 512
12
13 static char s_buffer[1000000];
14
15 static int ReadString( FILE *fp, char *buffer )
16 {
17         int i = 0;
18         int bytesRead = 0;
19
20         do
21         {
22                 fread( &buffer[i], 1, sizeof( char ), fp );
23                 bytesRead++;
24         } while ( buffer[i++] != 0 );
25         buffer[i] = 0;
26
27         return bytesRead;
28 }
29
30 static int ReadChunkAndLength( FILE *fp, short *chunk, long *len )
31 {
32         if ( fread( chunk, sizeof( short ), 1, fp ) != 1 )
33                 return 0;
34         if ( fread( len, sizeof( long ), 1, fp ) != 1 )
35                 Error( "Unexpected EOF found" );
36         return 1;
37 }
38
39 static void LoadMapName( FILE *fp, char *buffer, int thisChunkLen )
40 {
41         unsigned short chunkID;
42         long chunkLen;
43         long bytesRead = 0;
44
45         while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) )
46         {
47                 switch ( chunkID )
48                 {
49                 case _3DS_CHUNK_MAT_MAPNAME:
50                         fread( buffer, chunkLen - 6, 1, fp );
51                         break;
52                 default:
53                         fread( s_buffer, chunkLen - 6, 1, fp );
54                         break;
55                 }
56                 bytesRead += chunkLen;
57                 if ( bytesRead >= thisChunkLen )
58                         return;
59         }
60 }
61
62 static void LoadMaterialList( FILE *fp, long thisChunkLen, _3DSMaterial_t *pMat )
63 {
64         long chunkLen;
65         unsigned short chunkID;
66         long bytesRead = 0;
67         _3DSMaterial_t mat;
68         char curdir[1024];
69         char buffer[2048];
70
71         memset( &mat, 0, sizeof( mat ) );
72
73         if ( s_verbose )
74                 printf( "    >>> MATERIAL LIST\n" );
75
76         while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) )
77         {
78                 switch ( chunkID )
79                 {
80                         case _3DS_CHUNK_MAT_NAME:
81                                 fread( mat.name, chunkLen - 6, 1, fp );
82                                 if ( s_verbose )
83                                         printf( "        found mat name '%s'\n", mat.name );
84                                 break;
85                         case _3DS_CHUNK_TEXMAP:
86                                 LoadMapName( fp, mat.texture, chunkLen - 6 );
87                                 if ( s_verbose )
88                                         printf( "        found texture '%s'\n", mat.texture );
89                                 break;
90                         case _3DS_CHUNK_SPECMAP:
91                                 LoadMapName( fp, mat.specular, chunkLen - 6 );
92                                 if ( s_verbose )
93                                         printf( "        found specular map '%s'\n", mat.specular );
94                                 break;
95                         case _3DS_CHUNK_OPACMAP:
96                                 LoadMapName( fp, mat.opacity, chunkLen - 6 );
97                                 if ( s_verbose )
98                                         printf( "        found opacity map '%s'\n", mat.opacity );
99                                 break;
100                         case _3DS_CHUNK_REFLMAP:
101                                 LoadMapName( fp, mat.reflection, chunkLen - 6 );
102                                 if ( s_verbose )
103                                         printf( "        found reflection map '%s'\n", mat.reflection );
104                                 break;
105                         case _3DS_CHUNK_BUMPMAP:
106                                 LoadMapName( fp, mat.bump, chunkLen - 6 );
107                                 if ( s_verbose )
108                                         printf( "        found bump map '%s'\n", mat.bump );
109                                 break;
110                         default:
111                                 fread( s_buffer, chunkLen - 6, 1, fp );
112                                 break;
113                 }
114
115                 bytesRead += chunkLen;
116
117                 if ( bytesRead >= thisChunkLen )
118                         break;
119         }
120
121         Q_getwd( curdir );
122
123         if ( mat.texture[0] )
124         {
125                 sprintf( buffer, "%s%s", curdir, mat.texture );
126                 if ( strstr( buffer, gamedir + 1 ) )
127                         strcpy( mat.texture, strstr( buffer, gamedir + 1 ) + strlen( gamedir ) - 1 );
128                 else
129                         strcpy( mat.texture, buffer );
130         }
131
132         if ( mat.specular[0] )
133         {
134                 sprintf( buffer, "%s%s", curdir, mat.specular );
135                 if ( strstr( buffer, gamedir + 1 ) )
136                         strcpy( mat.specular, strstr( buffer, gamedir + 1 ) + strlen( gamedir ) - 1 );
137                 else
138                         strcpy( mat.specular, buffer );
139         }
140
141         if ( mat.bump[0] )
142         {
143                 sprintf( buffer, "%s%s", curdir, mat.bump );
144                 if ( strstr( buffer, gamedir + 1 ) )
145                         strcpy( mat.bump, strstr( buffer, gamedir + 1 ) + strlen( gamedir ) - 1 );
146                 else
147                         strcpy( mat.bump, buffer );
148         }
149
150         if ( mat.reflection[0] )
151         {
152                 sprintf( buffer, "%s%s", curdir, mat.reflection );
153                 if ( strstr( buffer, gamedir + 1 ) )
154                         strcpy( mat.reflection, strstr( buffer, gamedir + 1 ) + strlen( gamedir ) - 1 );
155                 else
156                         strcpy( mat.reflection, buffer );
157         }
158
159         if ( mat.opacity[0] )
160         {
161                 sprintf( buffer, "%s%s", curdir, mat.opacity );
162                 if ( strstr( buffer, gamedir + 1 ) )
163                         strcpy( mat.opacity, strstr( buffer, gamedir + 1 ) + strlen( gamedir ) - 1 );
164                 else
165                         strcpy( mat.opacity, buffer );
166         }
167
168         *pMat = mat;
169 }
170
171 static void LoadMeshMaterialGroup( FILE *fp, long thisChunkLen, _3DSMeshMaterialGroup_t *pMMG )
172 {
173         _3DSMeshMaterialGroup_t mmg;
174
175         memset( &mmg, 0, sizeof( mmg ) );
176
177         ReadString( fp, mmg.name );
178
179         fread( &mmg.numFaces, sizeof( mmg.numFaces ), 1, fp );
180         mmg.pFaces = malloc( sizeof( mmg.pFaces[0] ) * mmg.numFaces );
181         fread( mmg.pFaces, sizeof( mmg.pFaces[0] ), mmg.numFaces, fp );
182
183         if ( s_verbose )
184         {
185                 printf( "    >>> MESH MATERIAL GROUP '%s' (%d faces)\n", mmg.name, mmg.numFaces );
186
187                 {
188                         int i;
189
190                         for ( i = 0; i < mmg.numFaces; i++ )
191                         {
192                                 printf( "        %d\n", mmg.pFaces[i] );
193                         }
194                 }
195         }
196
197         *pMMG = mmg;
198 }
199
200 static void LoadNamedTriObject( FILE *fp, long thisChunkLen, _3DSTriObject_t *pTO )
201 {
202         long chunkLen;
203         unsigned short chunkID;
204         int i = 0;
205         long bytesRead = 0;
206         _3DSTriObject_t triObj;
207         _3DSMeshMaterialGroup_t meshMaterialGroups[MAX_MESH_MATERIAL_GROUPS];
208         int numMeshMaterialGroups = 0;
209
210         memset( &triObj, 0, sizeof( triObj ) );
211
212         if ( s_verbose )
213                 printf( "        >>> NAMED TRI OBJECT\n" );
214
215         while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) )
216         {
217                 switch ( chunkID )
218                 {
219                 case _3DS_CHUNK_MSH_MAT_GROUP:
220                         LoadMeshMaterialGroup( fp, chunkLen - 6, &meshMaterialGroups[numMeshMaterialGroups] );
221                         bytesRead += chunkLen;
222                         numMeshMaterialGroups++;
223                         break;
224                 case _3DS_CHUNK_FACE_ARRAY:
225                         fread( &triObj.numFaces, sizeof( triObj.numFaces ), 1, fp );
226                         assert( triObj.pFaces == 0 );
227
228                         triObj.pFaces = malloc( sizeof( triObj.pFaces[0] ) * triObj.numFaces );
229                         fread( triObj.pFaces, sizeof( triObj.pFaces[0] ), triObj.numFaces, fp );
230                         bytesRead += sizeof( triObj.numFaces ) + triObj.numFaces * sizeof( triObj.pFaces[0] ) + 6;
231
232                         if ( s_verbose )
233                         {
234                                 printf( "            found face array with %d faces\n", triObj.numFaces );
235                                 for ( i = 0; i < triObj.numFaces; i++ )
236                                 {
237                                         printf( "                %d: %d,%d,%d\n", i, triObj.pFaces[i].a, triObj.pFaces[i].b, triObj.pFaces[i].c );
238                                 }
239                         }
240
241                         break;
242                 case _3DS_CHUNK_POINT_ARRAY:
243                         fread( &triObj.numPoints, sizeof( triObj.numPoints ), 1, fp );
244                         triObj.pPoints = malloc( sizeof( triObj.pPoints[0] ) * triObj.numPoints );
245                         fread( triObj.pPoints, sizeof( triObj.pPoints[0] ), triObj.numPoints, fp );
246                         bytesRead += sizeof( triObj.numPoints ) + triObj.numPoints * sizeof( triObj.pPoints[0] ) + 6;
247
248                         // flip points around into our coordinate system
249                         for ( i = 0; i < triObj.numPoints; i++ )
250                         {
251                                 float x, y, z;
252
253                                 x = triObj.pPoints[i].x;
254                                 y = triObj.pPoints[i].y;
255                                 z = triObj.pPoints[i].z;
256
257                                 triObj.pPoints[i].x = -y;
258                                 triObj.pPoints[i].y = x;
259                                 triObj.pPoints[i].z = z;
260                         }
261
262                         if ( s_verbose )
263                         {
264                                 printf( "            found point array with %d points\n", triObj.numPoints );
265                                 for ( i = 0; i < triObj.numPoints; i++ )
266                                 {
267                                         printf( "                %d: %f,%f,%f\n", i, triObj.pPoints[i].x, triObj.pPoints[i].y, triObj.pPoints[i].z );
268                                 }
269                         }
270                         break;
271                 case _3DS_CHUNK_TEX_VERTS:
272                         fread( &triObj.numTexVerts, sizeof( triObj.numTexVerts ), 1, fp );
273                         triObj.pTexVerts = malloc( sizeof( triObj.pTexVerts[0] ) * triObj.numTexVerts );
274                         fread( triObj.pTexVerts, sizeof( triObj.pTexVerts[0] ), triObj.numTexVerts, fp );
275                         bytesRead += sizeof( triObj.numTexVerts ) + sizeof( triObj.pTexVerts[0] ) * triObj.numTexVerts + 6;
276
277                         if ( s_verbose )
278                         {
279                                 printf( "            found tex vert array with %d tex verts\n", triObj.numTexVerts );
280                                 for ( i = 0; i < triObj.numTexVerts; i++ )
281                                 {
282                                         printf( "                %d: %f,%f\n", i, triObj.pTexVerts[i].s, triObj.pTexVerts[i].t );
283                                 }
284                         }
285                         break;
286                 default:
287                         fread( s_buffer, chunkLen - 6, 1, fp );
288                         bytesRead += chunkLen;
289                         break;
290                 }
291
292                 if ( bytesRead >= thisChunkLen )
293                         break;
294         }
295         *pTO = triObj;
296
297         if ( numMeshMaterialGroups == 0 )
298         {
299                 numMeshMaterialGroups = 1;
300                 strcpy( meshMaterialGroups[0].name, "(null)" );
301                 if ( pTO->numTexVerts ) {
302                         printf( "Warning: assigning (null) skin to tri object\n" );
303                 }
304         }
305         else
306         {
307                 assert( pTO->numFaces == meshMaterialGroups[0].numFaces );
308         }
309
310         pTO->pMeshMaterialGroups = malloc( sizeof( _3DSMeshMaterialGroup_t ) * numMeshMaterialGroups );
311         memcpy( pTO->pMeshMaterialGroups, meshMaterialGroups, numMeshMaterialGroups * sizeof( meshMaterialGroups[0] ) );
312         pTO->numMeshMaterialGroups = numMeshMaterialGroups;
313
314         //
315         // sanity checks
316         //
317         assert( numMeshMaterialGroups <= 1 );
318 }
319
320 static void LoadNamedObject( FILE *fp, long thisChunkLen, _3DSNamedObject_t *pNO )
321 {
322         long chunkLen;
323         unsigned short chunkID;
324         int i = 0;
325         long bytesRead = 0;
326         char name[100];
327         _3DSTriObject_t triObj[MAX_TRI_OBJECTS];
328         int numTriObjects = 0;
329
330         memset( triObj, 0, sizeof( triObj ) );
331
332         bytesRead += ReadString( fp, name );
333
334         if ( s_verbose )
335                 printf( "    >>> NAMED OBJECT '%s'\n", name );
336
337         while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) )
338         {
339                 switch ( chunkID )
340                 {
341                 case _3DS_CHUNK_NAMED_TRI_OBJECT:
342                         LoadNamedTriObject( fp, chunkLen - 6, &triObj[numTriObjects] );
343                         numTriObjects++;
344                         break;
345                 default:
346                         fread( s_buffer, chunkLen - 6, 1, fp );
347                         break;
348                 }
349
350                 bytesRead += chunkLen;
351
352                 if ( bytesRead >= thisChunkLen )
353                         break;
354         }
355
356         strcpy( pNO->name, name );
357         pNO->pTriObjects = malloc( sizeof( _3DSTriObject_t ) * numTriObjects );
358         memcpy( pNO->pTriObjects, triObj, sizeof( triObj[0] ) * numTriObjects );
359         pNO->numTriObjects = numTriObjects;
360
361         assert( numTriObjects <= 1 );
362 }
363
364 static void LoadEditChunk( FILE *fp, long thisChunkLen, _3DSEditChunk_t *pEC )
365 {
366         unsigned short chunkID;
367         long chunkLen;
368         long bytesRead = 0;
369         _3DSEditChunk_t editChunk;
370
371         _3DSMaterial_t mat[MAX_MATERIALS];
372         _3DSNamedObject_t namedObjects[MAX_NAMED_OBJECTS];
373
374         int numMaterials = 0, numNamedObjects = 0;
375
376         memset( &editChunk, 0, sizeof( editChunk ) );
377
378         if ( s_verbose )
379                 printf( ">>> EDIT CHUNK\n" );
380
381         while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) )
382         {
383                 switch ( chunkID )
384                 {
385                 case _3DS_CHUNK_MAT_LIST:
386                         LoadMaterialList( fp, chunkLen - 6, &mat[numMaterials] );
387                         numMaterials++;
388                         break;
389                 case _3DS_CHUNK_NAMED_OBJECT:
390                         LoadNamedObject( fp, chunkLen - 6, &namedObjects[numNamedObjects] );
391                         if ( namedObjects[numNamedObjects].numTriObjects != 0 )
392                                 ++numNamedObjects;
393                         break;
394                 case _3DS_CHUNK_MESH_VERSION:
395                 default:
396                         fread( s_buffer, chunkLen - 6, 1, fp );
397                         break;
398                 }
399
400                 bytesRead += chunkLen;
401
402                 if ( bytesRead >= thisChunkLen )
403                         break;
404         }
405
406         if ( numMaterials == 0 )
407         {
408                 numMaterials = 1;
409                 strcpy( mat[0].name, "(null)" );
410                 printf( "Warning: no material definitions found\n" );
411         }
412
413         pEC->numNamedObjects = numNamedObjects;
414
415         pEC->pMaterials = malloc( sizeof( _3DSMaterial_t ) * numMaterials );
416         pEC->pNamedObjects = malloc( sizeof( _3DSNamedObject_t ) * numNamedObjects );
417
418         memcpy( pEC->pMaterials, mat, numMaterials * sizeof( mat[0] ) );
419         memcpy( pEC->pNamedObjects, namedObjects, numNamedObjects * sizeof( namedObjects[0] ) );
420 }
421
422 static void Load3DS( const char *filename, _3DS_t *p3DS, qboolean verbose )
423 {
424         FILE *fp;
425         unsigned short chunkID;
426         long  chunkLen;
427         _3DSEditChunk_t editChunk;
428
429         s_verbose = verbose;
430
431         if ( ( fp = fopen( filename, "rb" ) ) == 0 )
432                 Error( "Unable to open '%s'", filename );
433
434         // read magic number
435         if ( ( fread( &chunkID, sizeof( short ), 1, fp ) != 1 ) ||
436                  ( LittleShort( chunkID ) != _3DS_CHUNK_MAGIC ) )
437         {
438                 Error( "Missing or incorrect magic number in '%s'", filename );
439         }
440         if ( fread( &chunkLen, sizeof( chunkLen ), 1, fp ) != 1 )
441                 Error( "Unexpected EOF encountered in '%s'", filename );
442         // version number
443         if ( !ReadChunkAndLength( fp, &chunkID, &chunkLen ) )
444                 Error( "Missing version number in '%s'", filename );
445         if ( fread( s_buffer, chunkLen - 6, 1, fp ) != 1 )
446                 Error( "Unexpected EOF encountered in '%s'", filename );
447
448         while ( ReadChunkAndLength( fp, &chunkID, &chunkLen ) )
449         {
450                 switch ( chunkID )
451                 {
452                         case _3DS_CHUNK_EDIT:
453                                 LoadEditChunk( fp, chunkLen - 6, &editChunk );
454                                 break;
455                         case _3DS_CHUNK_KEYFRAME_DATA:
456                                 fread( s_buffer, chunkLen - 6, 1, fp );
457                                 break;
458                         default:
459                                 fread( s_buffer, chunkLen - 6, 1, fp );
460                                 break;
461                 }
462         }
463
464         fclose( fp );
465
466         p3DS->editChunk = editChunk;
467 }
468
469 static void ComputeNormals( _3DSTriObject_t *pTO, triangle_t *pTris )
470 {
471         vec3_t faceNormals[POLYSET_MAXTRIANGLES];
472         vec3_t vertexNormals[POLYSET_MAXTRIANGLES*3];
473         vec3_t side0, side1, facenormal;
474         int f, v;
475
476         memset( faceNormals, 0, sizeof( faceNormals ) );
477         memset( vertexNormals, 0, sizeof( vertexNormals ) );
478
479         //
480         // compute face normals
481         //
482         for ( f = 0; f < pTO->numFaces; f++ )
483         {
484                 VectorSubtract( pTris[f].verts[0], pTris[f].verts[1], side0 );
485                 VectorSubtract( pTris[f].verts[2], pTris[f].verts[1], side1 );
486
487                 CrossProduct( side0, side1, facenormal );
488                 VectorNormalize( facenormal, faceNormals[f] );
489         }
490
491         //
492         // sum vertex normals
493         //
494         for ( v = 0; v < pTO->numPoints; v++ )
495         {
496                 for ( f = 0; f < pTO->numFaces; f++ )
497                 {
498                         if ( ( pTO->pFaces[f].a == v ) ||
499                                  ( pTO->pFaces[f].b == v ) ||
500                                  ( pTO->pFaces[f].c == v ) )
501                         {
502                                 vertexNormals[v][0] += faceNormals[f][0];
503                                 vertexNormals[v][1] += faceNormals[f][1];
504                                 vertexNormals[v][2] += faceNormals[f][2];
505                         }
506                 }
507
508                 VectorNormalize( vertexNormals[v], vertexNormals[v] );
509         }
510
511         //
512         // copy vertex normals into triangles
513         //
514         for ( f = 0; f < pTO->numFaces; f++ )
515         {
516                 int i0 = pTO->pFaces[f].c;
517                 int i1 = pTO->pFaces[f].b;
518                 int i2 = pTO->pFaces[f].a;
519
520                 VectorCopy( vertexNormals[i0], pTris[f].normals[0] );
521                 VectorCopy( vertexNormals[i1], pTris[f].normals[1] );
522                 VectorCopy( vertexNormals[i2], pTris[f].normals[2] );
523         }
524 }
525
526 /*
527 ** void _3DS_LoadPolysets
528 */
529 void _3DS_LoadPolysets( const char *filename, polyset_t **ppPSET, int *numpsets, qboolean verbose )
530 {
531         _3DS_t _3ds;
532         int numPolysets;
533         polyset_t *pPSET;
534         triangle_t *ptri, *triangles;
535         int i;
536
537         // load the 3DS
538         memset( &_3ds, 0, sizeof( _3ds ) );
539         Load3DS( filename, &_3ds, verbose );
540
541         // compute information
542         numPolysets = _3ds.editChunk.numNamedObjects;
543
544         // allocate memory
545         pPSET = calloc( 1, numPolysets * sizeof( polyset_t ) );
546         triangles = ptri = calloc( 1, POLYSET_MAXTRIANGLES * sizeof( triangle_t ) );
547
548         // copy the data over
549         for ( i = 0; i < numPolysets; i++ )
550         {
551                 char matnamebuf[1024];
552                 int j;
553                 triangle_t *tri;
554                 _3DSTriObject_t *pTO = &_3ds.editChunk.pNamedObjects[i].pTriObjects[0];
555
556                 pPSET[i].triangles = ptri;
557                 pPSET[i].numtriangles = pTO->numFaces;
558                 strcpy( pPSET[i].name, _3ds.editChunk.pNamedObjects[i].name );
559
560                 strcpy( matnamebuf, filename );
561                 if ( strrchr( matnamebuf, '/' ) )
562                         *( strrchr( matnamebuf, '/' ) + 1 )= 0;
563                 strcat( matnamebuf, pTO->pMeshMaterialGroups[0].name );
564
565                 if ( strstr( matnamebuf, gamedir ) )
566                         strcpy( pPSET[i].materialname, strstr( matnamebuf, gamedir ) + strlen( gamedir ) );
567                 else
568                         strcpy( pPSET[i].materialname, pTO->pMeshMaterialGroups[0].name );
569
570                 assert( pPSET[i].numtriangles < POLYSET_MAXTRIANGLES );
571
572                 for ( tri = ptri, j = 0; j < pPSET[i].numtriangles; j++ )
573                 {
574                         int i0 = pTO->pFaces[j].c;
575                         int i1 = pTO->pFaces[j].b;
576                         int i2 = pTO->pFaces[j].a;
577
578                         tri->verts[0][0] = pTO->pPoints[i0].x;
579                         tri->verts[0][1] = pTO->pPoints[i0].y;
580                         tri->verts[0][2] = pTO->pPoints[i0].z;
581
582                         tri->verts[1][0] = pTO->pPoints[i1].x;
583                         tri->verts[1][1] = pTO->pPoints[i1].y;
584                         tri->verts[1][2] = pTO->pPoints[i1].z;
585
586                         tri->verts[2][0] = pTO->pPoints[i2].x;
587                         tri->verts[2][1] = pTO->pPoints[i2].y;
588                         tri->verts[2][2] = pTO->pPoints[i2].z;
589 /*
590                         for ( k = 0; k < 3; k++ )
591                         {
592                                 tri->colors[0][k] = 1;
593                                 tri->colors[1][k] = 1;
594                                 tri->colors[2][k] = 1;
595                         }
596 */
597
598                         if ( pTO->pTexVerts )
599                         {
600                                 tri->texcoords[0][0] = pTO->pTexVerts[i0].s;
601                                 tri->texcoords[0][1] = 1.0f - pTO->pTexVerts[i0].t;
602                                 tri->texcoords[1][0] = pTO->pTexVerts[i1].s;
603                                 tri->texcoords[1][1] = 1.0f - pTO->pTexVerts[i1].t;
604                                 tri->texcoords[2][0] = pTO->pTexVerts[i2].s;
605                                 tri->texcoords[2][1] = 1.0f - pTO->pTexVerts[i2].t;
606                         }
607
608                         tri++;
609                 }
610
611                 ptri += pPSET[i].numtriangles;
612                 assert( ptri - triangles < POLYSET_MAXTRIANGLES );
613         }
614
615         // compute normal data
616 #if 0
617         for ( i = 0; i < numPolysets; i++ )
618         {
619                 // unique vertices based solely on vertex position
620                 ComputeNormals( &_3ds.editChunk.pNamedObjects[i].pTriObjects[0],
621                                                  pPSET[i].triangles );
622         }
623 #endif
624
625         free( _3ds.editChunk.pMaterials );
626         free( _3ds.editChunk.pNamedObjects );
627
628         *ppPSET = pPSET;
629         *numpsets = numPolysets;
630 }