]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - libs/picomodel/pm_obj.c
transfer from internal tree r5311 branches/1.4-gpl
[xonotic/netradiant.git] / libs / picomodel / pm_obj.c
1 /* -----------------------------------------------------------------------------\r
2 \r
3 PicoModel Library\r
4 \r
5 Copyright (c) 2002, Randy Reddig & seaw0lf\r
6 All rights reserved.\r
7 \r
8 Redistribution and use in source and binary forms, with or without modification,\r
9 are permitted provided that the following conditions are met:\r
10 \r
11 Redistributions of source code must retain the above copyright notice, this list\r
12 of conditions and the following disclaimer.\r
13 \r
14 Redistributions in binary form must reproduce the above copyright notice, this\r
15 list of conditions and the following disclaimer in the documentation and/or\r
16 other materials provided with the distribution.\r
17 \r
18 Neither the names of the copyright holders nor the names of its contributors may\r
19 be used to endorse or promote products derived from this software without\r
20 specific prior written permission.\r
21 \r
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND\r
23 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
24 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\r
25 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\r
26 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\r
27 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\r
28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\r
29 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\r
30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
31 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
32 \r
33 ----------------------------------------------------------------------------- */\r
34 \r
35 \r
36 \r
37 /* marker */\r
38 #define PM_OBJ_C\r
39 \r
40 /* dependencies */\r
41 #include "picointernal.h"\r
42 \r
43 /* disable warnings */\r
44 #ifdef _WIN32\r
45 #pragma warning( disable:4100 )         /* unref param */\r
46 #endif\r
47 \r
48 /* todo:\r
49  * - '_obj_load' code crashes in a weird way after\r
50  *   '_obj_mtl_load' for a few .mtl files\r
51  * - process 'mtllib' rather than using <model>.mtl\r
52  * - handle 'usemtl' statements\r
53  */\r
54 /* uncomment when debugging this module */\r
55 /* #define DEBUG_PM_OBJ */\r
56 /* #define DEBUG_PM_OBJ_EX */\r
57 \r
58 /* this holds temporary vertex data read by parser */\r
59 typedef struct SObjVertexData\r
60 {\r
61         picoVec3_t      v;                      /* geometric vertices */\r
62         picoVec2_t      vt;                     /* texture vertices */\r
63         picoVec3_t      vn;                     /* vertex normals (optional) */\r
64 }\r
65 TObjVertexData;\r
66 \r
67 /* _obj_canload:\r
68  *  validates a wavefront obj model file.\r
69  */\r
70 static int _obj_canload( PM_PARAMS_CANLOAD )\r
71 {\r
72         picoParser_t *p;\r
73 \r
74         /* check data length */\r
75         if (bufSize < 30)\r
76                 return PICO_PMV_ERROR_SIZE;\r
77 \r
78         /* first check file extension. we have to do this for objs */\r
79         /* cause there is no good way to identify the contents */\r
80         if (_pico_stristr(fileName,".obj") != NULL ||\r
81                 _pico_stristr(fileName,".wf" ) != NULL)\r
82         {\r
83                 return PICO_PMV_OK;\r
84         }\r
85         /* if the extension check failed we parse through the first */\r
86         /* few lines in file and look for common keywords often */\r
87         /* appearing at the beginning of wavefront objects */\r
88 \r
89         /* alllocate a new pico parser */\r
90         p = _pico_new_parser( (picoByte_t *)buffer,bufSize );\r
91         if (p == NULL)\r
92                 return PICO_PMV_ERROR_MEMORY;\r
93 \r
94         /* parse obj head line by line for type check */\r
95         while( 1 )\r
96         {\r
97                 /* get first token on line */\r
98                 if (_pico_parse_first( p ) == NULL)\r
99                         break;\r
100 \r
101                 /* we only parse the first few lines, say 80 */\r
102                 if (p->curLine > 80)\r
103                         break;\r
104 \r
105                 /* skip empty lines */\r
106                 if (p->token == NULL || !strlen( p->token ))\r
107                         continue;\r
108 \r
109                 /* material library keywords are teh good */\r
110                 if (!_pico_stricmp(p->token,"usemtl") ||\r
111                         !_pico_stricmp(p->token,"mtllib") ||\r
112                         !_pico_stricmp(p->token,"g") ||\r
113                         !_pico_stricmp(p->token,"v"))   /* v,g bit fishy, but uh... */\r
114                 {\r
115                         /* free the pico parser thing */\r
116                         _pico_free_parser( p );\r
117 \r
118                         /* seems to be a valid wavefront obj */\r
119                         return PICO_PMV_OK;\r
120                 }\r
121                 /* skip rest of line */\r
122                 _pico_parse_skip_rest( p );\r
123         }\r
124         /* free the pico parser thing */\r
125         _pico_free_parser( p );\r
126 \r
127         /* doesn't really look like an obj to us */\r
128         return PICO_PMV_ERROR;\r
129 }\r
130 \r
131 /* SizeObjVertexData:\r
132  *   This pretty piece of 'alloc ahead' code dynamically\r
133  *   allocates - and reallocates as soon as required -\r
134  *   my vertex data array in even steps.\r
135  */\r
136 #define SIZE_OBJ_STEP  4096\r
137 \r
138 static TObjVertexData *SizeObjVertexData(\r
139         TObjVertexData *vertexData, int reqEntries,\r
140         int *entries, int *allocated)\r
141 {\r
142         int newAllocated;\r
143 \r
144         /* sanity checks */\r
145         if (reqEntries < 1)\r
146                 return NULL;\r
147         if (entries == NULL || allocated == NULL)\r
148                 return NULL; /* must have */\r
149 \r
150         /* no need to grow yet */\r
151         if (vertexData && (reqEntries < *allocated))\r
152         {\r
153                 *entries = reqEntries;\r
154                 return vertexData;\r
155         }\r
156         /* given vertex data ptr not allocated yet */\r
157         if (vertexData == NULL)\r
158         {\r
159                 /* how many entries to allocate */\r
160                 newAllocated = (reqEntries > SIZE_OBJ_STEP) ?\r
161                                                 reqEntries : SIZE_OBJ_STEP;\r
162 \r
163                 /* throw out an extended debug message */\r
164 #ifdef DEBUG_PM_OBJ_EX\r
165                 printf("SizeObjVertexData: allocate (%d entries)\n",\r
166                         newAllocated);\r
167 #endif\r
168                 /* first time allocation */\r
169                 vertexData = (TObjVertexData *)\r
170                         _pico_alloc( sizeof(TObjVertexData) * newAllocated );\r
171 \r
172                 /* allocation failed */\r
173                 if (vertexData == NULL)\r
174                         return NULL;\r
175 \r
176                 /* allocation succeeded */\r
177                 *allocated = newAllocated;\r
178                 *entries   = reqEntries;\r
179                 return vertexData;\r
180         }\r
181         /* given vertex data ptr needs to be resized */\r
182         if (reqEntries == *allocated)\r
183         {\r
184                 newAllocated = (*allocated + SIZE_OBJ_STEP);\r
185 \r
186                 /* throw out an extended debug message */\r
187 #ifdef DEBUG_PM_OBJ_EX\r
188                 printf("SizeObjVertexData: reallocate (%d entries)\n",\r
189                         newAllocated);\r
190 #endif\r
191                 /* try to reallocate */\r
192                 vertexData = (TObjVertexData *)\r
193                         _pico_realloc( (void *)&vertexData,\r
194                                 sizeof(TObjVertexData) * (*allocated),\r
195                                 sizeof(TObjVertexData) * (newAllocated));\r
196 \r
197                 /* reallocation failed */\r
198                 if (vertexData == NULL)\r
199                         return NULL;\r
200 \r
201                 /* reallocation succeeded */\r
202                 *allocated = newAllocated;\r
203                 *entries   = reqEntries;\r
204                 return vertexData;\r
205         }\r
206         /* we're b0rked when we reach this */\r
207         return NULL;\r
208 }\r
209 \r
210 static void FreeObjVertexData( TObjVertexData *vertexData )\r
211 {\r
212         if (vertexData != NULL)\r
213         {\r
214                 free( (TObjVertexData *)vertexData );\r
215         }\r
216 }\r
217 \r
218 static int _obj_mtl_load( picoModel_t *model )\r
219 {\r
220         picoShader_t *curShader = NULL;\r
221         picoParser_t *p;\r
222         picoByte_t   *mtlBuffer;\r
223         int                       mtlBufSize;\r
224         char             *fileName;\r
225 \r
226         /* sanity checks */\r
227         if( model == NULL || model->fileName == NULL )\r
228                 return 0;\r
229 \r
230         /* skip if we have a zero length model file name */\r
231         if (!strlen( model->fileName ))\r
232                 return 0;\r
233 \r
234         /* helper */\r
235         #define _obj_mtl_error_return \\r
236         { \\r
237                 _pico_free_parser( p ); \\r
238                 _pico_free_file( mtlBuffer ); \\r
239                 _pico_free( fileName ); \\r
240                 return 0; \\r
241         }\r
242         /* alloc copy of model file name */\r
243         fileName = _pico_clone_alloc( model->fileName,-1 );\r
244         if (fileName == NULL)\r
245                 return 0;\r
246 \r
247         /* change extension of model file to .mtl */\r
248         _pico_setfext( fileName, "mtl" );\r
249 \r
250         /* load .mtl file contents */\r
251         _pico_load_file( fileName,&mtlBuffer,&mtlBufSize );\r
252 \r
253         /* check result */\r
254         if (mtlBufSize == 0) return 1;          /* file is empty: no error */\r
255         if (mtlBufSize  < 0) return 0;          /* load failed: error */\r
256 \r
257         /* create a new pico parser */\r
258         p = _pico_new_parser( mtlBuffer, mtlBufSize );\r
259         if (p == NULL)\r
260                 _obj_mtl_error_return;\r
261         \r
262         /* doo teh .mtl parse */\r
263         while( 1 )\r
264         {\r
265                 /* get next token in material file */\r
266                 if (_pico_parse( p,1 ) == NULL)\r
267                         break;\r
268 #if 0\r
269 \r
270                 /* skip empty lines */\r
271                 if (p->token == NULL || !strlen( p->token ))\r
272                         continue;\r
273 \r
274                 /* skip comment lines */\r
275                 if (p->token[0] == '#')\r
276                 {\r
277                         _pico_parse_skip_rest( p );\r
278                         continue;\r
279                 }\r
280                 /* new material */\r
281                 if (!_pico_stricmp(p->token,"newmtl"))\r
282                 {\r
283                         picoShader_t *shader;\r
284                         char *name;\r
285 \r
286                         /* get material name */\r
287                         name = _pico_parse( p,0 );\r
288 \r
289                         /* validate material name */\r
290                         if (name == NULL || !strlen(name))\r
291                         {\r
292                                 _pico_printf( PICO_ERROR,"Missing material name in MTL, line %d.",p->curLine);\r
293                                 _obj_mtl_error_return;\r
294                         }\r
295                         /* create a new pico shader */\r
296                         shader = PicoNewShader( model );\r
297                         if (shader == NULL)\r
298                                 _obj_mtl_error_return;\r
299 \r
300                         /* set shader name */\r
301                         PicoSetShaderName( shader,name );\r
302 \r
303                         /* assign pointer to current shader */\r
304                         curShader = shader;\r
305                 }\r
306                 /* diffuse map name */\r
307                 else if (!_pico_stricmp(p->token,"map_kd"))\r
308                 {\r
309                         char *mapName;\r
310 \r
311                         /* pointer to current shader must be valid */\r
312                         if (curShader == NULL)\r
313                                 _obj_mtl_error_return;\r
314 \r
315                         /* get material's diffuse map name */\r
316                         mapName = _pico_parse( p,0 );\r
317 \r
318                         /* validate map name */\r
319                         if (mapName == NULL || !strlen(mapName))\r
320                         {\r
321                                 _pico_printf( PICO_ERROR,"Missing material map name in MTL, line %d.",p->curLine);\r
322                                 _obj_mtl_error_return;\r
323                         }\r
324                         /* set shader map name */\r
325                         PicoSetShaderMapName( shader,mapName );\r
326                 }\r
327                 /* dissolve factor (pseudo transparency 0..1) */\r
328                 /* where 0 means 100% transparent and 1 means opaque */\r
329                 else if (!_pico_stricmp(p->token,"d"))\r
330                 {\r
331                         picoByte_t *diffuse;\r
332                         float value;\r
333 \r
334 \r
335                         /* get dissolve factor */\r
336                         if (!_pico_parse_float( p,&value ))\r
337                                 _obj_mtl_error_return;\r
338 \r
339                         /* set shader transparency */\r
340                         PicoSetShaderTransparency( curShader,value );\r
341 \r
342                         /* get shader's diffuse color */\r
343                         diffuse = PicoGetShaderDiffuseColor( curShader );\r
344 \r
345                         /* set diffuse alpha to transparency */\r
346                         diffuse[ 3 ] = (picoByte_t)( value * 255.0 );\r
347 \r
348                         /* set shader's new diffuse color */\r
349                         PicoSetShaderDiffuseColor( curShader,diffuse );\r
350                 }\r
351                 /* shininess (phong specular component) */\r
352                 else if (!_pico_stricmp(p->token,"ns"))\r
353                 {\r
354                         /* remark:\r
355                          * - well, this is some major obj spec fuckup once again. some\r
356                          *   apps store this in 0..1 range, others use 0..100 range,\r
357                          *   even others use 0..2048 range, and again others use the\r
358                          *   range 0..128, some even use 0..1000, 0..200, 400..700,\r
359                          *   honestly, what's up with the 3d app coders? happens when\r
360                          *   you smoke too much weed i guess. -sea\r
361                          */\r
362                         float value;\r
363 \r
364                         /* pointer to current shader must be valid */\r
365                         if (curShader == NULL)\r
366                                 _obj_mtl_error_return;\r
367 \r
368                         /* get totally screwed up shininess (a random value in fact ;) */\r
369                         if (!_pico_parse_float( p,&value ))\r
370                                 _obj_mtl_error_return;\r
371 \r
372                         /* okay, there is no way to set this correctly, so we simply */\r
373                         /* try to guess a few ranges (most common ones i have seen) */\r
374 \r
375                         /* assume 0..2048 range */\r
376                         if (value > 1000)\r
377                                 value = 128.0 * (value / 2048.0);\r
378                         /* assume 0..1000 range */\r
379                         else if (value > 200)\r
380                                 value = 128.0 * (value / 1000.0);\r
381                         /* assume 0..200 range */\r
382                         else if (value > 100)\r
383                                 value = 128.0 * (value / 200.0);\r
384                         /* assume 0..100 range */\r
385                         else if (value > 1)\r
386                                 value = 128.0 * (value / 100.0);\r
387                         /* assume 0..1 range */\r
388                         else {\r
389                                 value *= 128.0;\r
390                         }\r
391                         /* negative shininess is bad (yes, i have seen it...) */\r
392                         if (value < 0.0) value = 0.0;\r
393 \r
394                         /* set the pico shininess value in range 0..127 */\r
395                         /* geez, .obj is such a mess... */\r
396                         PicoSetShaderShininess( curShader,value );\r
397                 }\r
398                 /* kol0r ambient (wut teh fuk does "ka" stand for?) */\r
399                 else if (!_pico_stricmp(p->token,"ka"))\r
400                 {\r
401                         picoColor_t color;\r
402                         picoVec3_t  v;\r
403 \r
404                         /* pointer to current shader must be valid */\r
405                         if (curShader == NULL)\r
406                                 _obj_mtl_error_return;\r
407 \r
408                         /* get color vector */\r
409                         if (!_pico_parse_vec( p,v ))\r
410                                 _obj_mtl_error_return;\r
411 \r
412                         /* scale to byte range */\r
413                         color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );\r
414                         color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );\r
415                         color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );\r
416                         color[ 3 ] = (picoByte_t)( 255 );\r
417 \r
418                         /* set ambient color */\r
419                         PicoSetShaderAmbientColor( curShader,color );\r
420                 }\r
421                 /* kol0r diffuse */\r
422                 else if (!_pico_stricmp(p->token,"kd"))\r
423                 {\r
424                         picoColor_t color;\r
425                         picoVec3_t  v;\r
426 \r
427                         /* pointer to current shader must be valid */\r
428                         if (curShader == NULL)\r
429                                 _obj_mtl_error_return;\r
430 \r
431                         /* get color vector */\r
432                         if (!_pico_parse_vec( p,v ))\r
433                                 _obj_mtl_error_return;\r
434 \r
435                         /* scale to byte range */\r
436                         color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );\r
437                         color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );\r
438                         color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );\r
439                         color[ 3 ] = (picoByte_t)( 255 );\r
440 \r
441                         /* set diffuse color */\r
442                         PicoSetShaderDiffuseColor( curShader,color );\r
443                 }\r
444                 /* kol0r specular */\r
445                 else if (!_pico_stricmp(p->token,"ks"))\r
446                 {\r
447                         picoColor_t color;\r
448                         picoVec3_t  v;\r
449 \r
450                         /* pointer to current shader must be valid */\r
451                         if (curShader == NULL)\r
452                                 _obj_mtl_error_return;\r
453 \r
454                         /* get color vector */\r
455                         if (!_pico_parse_vec( p,v ))\r
456                                 _obj_mtl_error_return;\r
457 \r
458                         /* scale to byte range */\r
459                         color[ 0 ] = (picoByte_t)( v[ 0 ] * 255 );\r
460                         color[ 1 ] = (picoByte_t)( v[ 1 ] * 255 );\r
461                         color[ 2 ] = (picoByte_t)( v[ 2 ] * 255 );\r
462                         color[ 3 ] = (picoByte_t)( 255 );\r
463 \r
464                         /* set specular color */\r
465                         PicoSetShaderSpecularColor( curShader,color );\r
466                 }\r
467 #endif\r
468                 /* skip rest of line */\r
469                 _pico_parse_skip_rest( p );\r
470         }\r
471 \r
472         /* free parser, file buffer, and file name */\r
473         _pico_free_parser( p );\r
474         _pico_free_file( mtlBuffer );\r
475         _pico_free( fileName );\r
476 \r
477         /* return with success */\r
478         return 1;\r
479 }\r
480 \r
481 /* _obj_load:\r
482  *  loads a wavefront obj model file.\r
483 */\r
484 static picoModel_t *_obj_load( PM_PARAMS_LOAD )\r
485 {\r
486         TObjVertexData *vertexData  = NULL;\r
487         picoModel_t    *model;\r
488         picoSurface_t  *curSurface  = NULL;\r
489         picoParser_t   *p;\r
490         int                             allocated;\r
491         int                         entries;\r
492         int                             numVerts        = 0;\r
493         int                             numNormals      = 0;\r
494         int                             numUVs          = 0;\r
495         int                             curVertex       = 0;\r
496         int                             curFace         = 0;\r
497 \r
498         /* helper */\r
499         #define _obj_error_return(m) \\r
500         { \\r
501                 _pico_printf( PICO_ERROR,"%s in OBJ, line %d.",m,p->curLine); \\r
502                 _pico_free_parser( p ); \\r
503                 FreeObjVertexData( vertexData ); \\r
504                 PicoFreeModel( model ); \\r
505                 return NULL; \\r
506         }\r
507         /* alllocate a new pico parser */\r
508         p = _pico_new_parser( (picoByte_t *)buffer,bufSize );\r
509         if (p == NULL) return NULL;\r
510 \r
511         /* create a new pico model */\r
512         model = PicoNewModel();\r
513         if (model == NULL)\r
514         {\r
515                 _pico_free_parser( p );\r
516                 return NULL;\r
517         }\r
518         /* do model setup */\r
519         PicoSetModelFrameNum( model,frameNum );\r
520         PicoSetModelName( model,fileName );\r
521         PicoSetModelFileName( model,fileName );\r
522 \r
523         /* try loading the materials; we don't handle the result */\r
524 #if 0\r
525         _obj_mtl_load( model );\r
526 #endif\r
527 \r
528         /* parse obj line by line */\r
529         while( 1 )\r
530         {\r
531                 /* get first token on line */\r
532                 if (_pico_parse_first( p ) == NULL)\r
533                         break;\r
534 \r
535                 /* skip empty lines */\r
536                 if (p->token == NULL || !strlen( p->token ))\r
537                         continue;\r
538 \r
539                 /* skip comment lines */\r
540                 if (p->token[0] == '#')\r
541                 {\r
542                         _pico_parse_skip_rest( p );\r
543                         continue;\r
544                 }\r
545                 /* vertex */\r
546                 if (!_pico_stricmp(p->token,"v"))\r
547                 {\r
548                         TObjVertexData *data;\r
549                         picoVec3_t v;\r
550 \r
551                         vertexData = SizeObjVertexData( vertexData,numVerts+1,&entries,&allocated );\r
552                         if (vertexData == NULL)\r
553                                 _obj_error_return("Realloc of vertex data failed (1)");\r
554 \r
555                         data = &vertexData[ numVerts++ ];\r
556 \r
557                         /* get and copy vertex */\r
558                         if (!_pico_parse_vec( p,v ))\r
559                                 _obj_error_return("Vertex parse error");\r
560 \r
561                         _pico_copy_vec( v,data->v );\r
562 \r
563 #ifdef DEBUG_PM_OBJ_EX\r
564                         printf("Vertex: x: %f y: %f z: %f\n",v[0],v[1],v[2]);\r
565 #endif\r
566                 }\r
567                 /* uv coord */\r
568                 else if (!_pico_stricmp(p->token,"vt"))\r
569                 {\r
570                         TObjVertexData *data;\r
571                         picoVec2_t coord;\r
572 \r
573                         vertexData = SizeObjVertexData( vertexData,numUVs+1,&entries,&allocated );\r
574                         if (vertexData == NULL)\r
575                                 _obj_error_return("Realloc of vertex data failed (2)");\r
576 \r
577                         data = &vertexData[ numUVs++ ];\r
578 \r
579                         /* get and copy tex coord */\r
580                         if (!_pico_parse_vec2( p,coord ))\r
581                                 _obj_error_return("UV coord parse error");\r
582 \r
583                         _pico_copy_vec2( coord,data->vt );\r
584 \r
585 #ifdef DEBUG_PM_OBJ_EX\r
586                         printf("TexCoord: u: %f v: %f\n",coord[0],coord[1]);\r
587 #endif\r
588                 }\r
589                 /* vertex normal */\r
590                 else if (!_pico_stricmp(p->token,"vn"))\r
591                 {\r
592                         TObjVertexData *data;\r
593                         picoVec3_t n;\r
594 \r
595                         vertexData = SizeObjVertexData( vertexData,numNormals+1,&entries,&allocated );\r
596                         if (vertexData == NULL)\r
597                                 _obj_error_return("Realloc of vertex data failed (3)");\r
598 \r
599                         data = &vertexData[ numNormals++ ];\r
600 \r
601                         /* get and copy vertex normal */\r
602                         if (!_pico_parse_vec( p,n ))\r
603                                 _obj_error_return("Vertex normal parse error");\r
604 \r
605                         _pico_copy_vec( n,data->vn );\r
606 \r
607 #ifdef DEBUG_PM_OBJ_EX\r
608                         printf("Normal: x: %f y: %f z: %f\n",n[0],n[1],n[2]);\r
609 #endif\r
610                 }\r
611                 /* new group (for us this means a new surface) */\r
612                 else if (!_pico_stricmp(p->token,"g"))\r
613                 {\r
614                         picoSurface_t *newSurface;\r
615                         char *groupName;\r
616 \r
617                         /* get first group name (ignore 2nd,3rd,etc.) */\r
618                         groupName = _pico_parse( p,0 );\r
619                         if (groupName == NULL || !strlen(groupName))\r
620                         {\r
621                                 /* some obj exporters feel like they don't need to */\r
622                                 /* supply a group name. so we gotta handle it here */\r
623 #if 1\r
624                                 strcpy( p->token,"default" );\r
625                                 groupName = p->token;\r
626 #else\r
627                                 _obj_error_return("Invalid or missing group name");\r
628 #endif\r
629                         }\r
630                         /* allocate a pico surface */\r
631                         newSurface = PicoNewSurface( model );\r
632                         if (newSurface == NULL)\r
633                                 _obj_error_return("Error allocating surface");\r
634 \r
635                         /* reset face index for surface */\r
636                         curFace = 0;\r
637 \r
638                         /* set ptr to current surface */\r
639                         curSurface = newSurface;\r
640 \r
641                         /* we use triangle meshes */\r
642                         PicoSetSurfaceType( newSurface,PICO_TRIANGLES );\r
643 \r
644                         /* set surface name */\r
645                         PicoSetSurfaceName( newSurface,groupName );\r
646 \r
647 #ifdef DEBUG_PM_OBJ_EX\r
648                         printf("Group: '%s'\n",groupName);\r
649 #endif\r
650                 }\r
651                 /* face (oh jesus, hopefully this will do the job right ;) */\r
652                 else if (!_pico_stricmp(p->token,"f"))\r
653                 {\r
654                         /* okay, this is a mess. some 3d apps seem to try being unique, */\r
655                         /* hello cinema4d & 3d exploration, feel good today?, and save */\r
656                         /* this crap in tons of different formats. gah, those screwed */\r
657                         /* coders. tho the wavefront obj standard defines exactly two */\r
658                         /* ways of storing face information. so, i really won't support */\r
659                         /* such stupid extravaganza here! */\r
660 \r
661                         picoVec3_t verts  [ 4 ];\r
662                         picoVec3_t normals[ 4 ];\r
663                         picoVec2_t coords [ 4 ];\r
664 \r
665                         int iv [ 4 ], has_v;\r
666                         int ivt[ 4 ], has_vt = 0;\r
667                         int ivn[ 4 ], has_vn = 0;\r
668                         int have_quad = 0;\r
669                         int slashcount;\r
670                         int doubleslash;\r
671                         int i;\r
672 \r
673                         /* group defs *must* come before faces */\r
674                         if (curSurface == NULL)\r
675                                 _obj_error_return("No group defined for faces");\r
676 \r
677 #ifdef DEBUG_PM_OBJ_EX\r
678                         printf("Face: ");\r
679 #endif\r
680                         /* read vertex/uv/normal indices for the first three face */\r
681                         /* vertices (cause we only support triangles) into 'i*[]' */\r
682                         /* store the actual vertex/uv/normal data in three arrays */\r
683                         /* called 'verts','coords' and 'normals'. */\r
684                         for (i=0; i<4; i++)\r
685                         {\r
686                                 char *str;\r
687 \r
688                                 /* get next vertex index string (different */\r
689                                 /* formats are handled below) */\r
690                                 str = _pico_parse( p,0 );\r
691                                 if (str == NULL)\r
692                                 {\r
693                                         /* just break for quads */\r
694                                         if (i == 3) break;\r
695 \r
696                                         /* error otherwise */\r
697                                         _obj_error_return("Face parse error");\r
698                                 }\r
699                                 /* if this is the fourth index string we're */\r
700                                 /* parsing we assume that we have a quad */\r
701                                 if (i == 3)\r
702                                         have_quad = 1;\r
703 \r
704                                 /* get slash count once */\r
705                                 if (i == 0)\r
706                                 {\r
707                                         slashcount  = _pico_strchcount( str,'/' );\r
708                                         doubleslash =  strstr(str,"//") != NULL;\r
709                                 }\r
710                                 /* handle format 'v//vn' */\r
711                                 if (doubleslash && (slashcount == 2))\r
712                                 {\r
713                                         has_v = has_vn = 1;\r
714                                         sscanf( str,"%d//%d",&iv[ i ],&ivn[ i ] );\r
715                                 }\r
716                                 /* handle format 'v/vt/vn' */\r
717                                 else if (!doubleslash && (slashcount == 2))\r
718                                 {\r
719                                         has_v = has_vt = has_vn = 1;\r
720                                         sscanf( str,"%d/%d/%d",&iv[ i ],&ivt[ i ],&ivn[ i ] );\r
721                                 }\r
722                                 /* handle format 'v/vt' (non-standard fuckage) */\r
723                                 else if (!doubleslash && (slashcount == 1))\r
724                                 {\r
725                                         has_v = has_vt = 1;\r
726                                         sscanf( str,"%d/%d",&iv[ i ],&ivt[ i ] );\r
727                                 }\r
728                                 /* else assume face format 'v' */\r
729                                 /* (must have been invented by some bored granny) */\r
730                                 else {\r
731                                         /* get single vertex index */\r
732                                         has_v = 1;\r
733                                         iv[ i ] = atoi( str );\r
734 \r
735                                         /* either invalid face format or out of range */\r
736                                         if (iv[ i ] == 0)\r
737                                                 _obj_error_return("Invalid face format");\r
738                                 }\r
739                                 /* fix useless back references */\r
740                                 /* todo: check if this works as it is supposed to */\r
741 \r
742                                 /* assign new indices */\r
743                                 if (iv [ i ] < 0) iv [ i ] = (numVerts   - iv [ i ]);\r
744                                 if (ivt[ i ] < 0) ivt[ i ] = (numUVs     - ivt[ i ]);\r
745                                 if (ivn[ i ] < 0) ivn[ i ] = (numNormals - ivn[ i ]);\r
746 \r
747                                 /* validate indices */\r
748                                 /* - commented out. index range checks will trigger\r
749                                 if (iv [ i ] < 1) iv [ i ] = 1;\r
750                                 if (ivt[ i ] < 1) ivt[ i ] = 1;\r
751                                 if (ivn[ i ] < 1) ivn[ i ] = 1;\r
752                                 */\r
753                                 /* set vertex origin */\r
754                                 if (has_v)\r
755                                 {\r
756                                         /* check vertex index range */\r
757                                         if (iv[ i ] < 1 || iv[ i ] > numVerts)\r
758                                                 _obj_error_return("Vertex index out of range");\r
759 \r
760                                         /* get vertex data */\r
761                                         verts[ i ][ 0 ] = vertexData[ iv[ i ] - 1 ].v[ 0 ];\r
762                                         verts[ i ][ 1 ] = vertexData[ iv[ i ] - 1 ].v[ 1 ];\r
763                                         verts[ i ][ 2 ] = vertexData[ iv[ i ] - 1 ].v[ 2 ];\r
764                                 }\r
765                                 /* set vertex normal */\r
766                                 if (has_vn)\r
767                                 {\r
768                                         /* check normal index range */\r
769                                         if (ivn[ i ] < 1 || ivn[ i ] > numNormals)\r
770                                                 _obj_error_return("Normal index out of range");\r
771 \r
772                                         /* get normal data */\r
773                                         normals[ i ][ 0 ] = vertexData[ ivn[ i ] - 1 ].vn[ 0 ];\r
774                                         normals[ i ][ 1 ] = vertexData[ ivn[ i ] - 1 ].vn[ 1 ];\r
775                                         normals[ i ][ 2 ] = vertexData[ ivn[ i ] - 1 ].vn[ 2 ];\r
776                                 }\r
777                                 /* set texture coordinate */\r
778                                 if (has_vt)\r
779                                 {\r
780                                         /* check uv index range */\r
781                                         if (ivt[ i ] < 1 || ivt[ i ] > numUVs)\r
782                                                 _obj_error_return("UV coord index out of range");\r
783 \r
784                                         /* get uv coord data */\r
785                                         coords[ i ][ 0 ] = vertexData[ ivt[ i ] - 1 ].vt[ 0 ];\r
786                                         coords[ i ][ 1 ] = vertexData[ ivt[ i ] - 1 ].vt[ 1 ];\r
787                                         coords[ i ][ 1 ] = -coords[ i ][ 1 ];\r
788                                 }\r
789 #ifdef DEBUG_PM_OBJ_EX\r
790                                 printf("(%4d",iv[ i ]);\r
791                                 if (has_vt) printf(" %4d",ivt[ i ]);\r
792                                 if (has_vn) printf(" %4d",ivn[ i ]);\r
793                                 printf(") ");\r
794 #endif\r
795                         }\r
796 #ifdef DEBUG_PM_OBJ_EX\r
797                         printf("\n");\r
798 #endif\r
799                         /* now that we have extracted all the indices and have */\r
800                         /* read the actual data we need to assign all the crap */\r
801                         /* to our current pico surface */\r
802                         if (has_v)\r
803                         {\r
804                                 int max = 3;\r
805                                 if (have_quad) max = 4;\r
806 \r
807                                 /* assign all surface information */\r
808                                 for (i=0; i<max; i++)\r
809                                 {\r
810                                         /*if( has_v  )*/ PicoSetSurfaceXYZ       ( curSurface,  (curVertex + i), verts  [ i ] );\r
811                                         /*if( has_vt )*/ PicoSetSurfaceST        ( curSurface,0,(curVertex + i), coords [ i ] );\r
812                                         /*if( has_vn )*/ PicoSetSurfaceNormal( curSurface,  (curVertex + i), normals[ i ] );\r
813                                 }\r
814                                 /* add our triangle (A B C) */\r
815                                 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );\r
816                                 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 1 ) );\r
817                                 PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 2 ) );\r
818                                 curFace++;\r
819 \r
820                                 /* if we don't have a simple triangle, but a quad... */\r
821                                 if (have_quad)\r
822                                 {\r
823                                         /* we have to add another triangle (2nd half of quad which is A C D) */\r
824                                         PicoSetSurfaceIndex( curSurface,(curFace * 3 + 2),(picoIndex_t)( curVertex + 0 ) );\r
825                                         PicoSetSurfaceIndex( curSurface,(curFace * 3 + 1),(picoIndex_t)( curVertex + 2 ) );\r
826                                         PicoSetSurfaceIndex( curSurface,(curFace * 3 + 0),(picoIndex_t)( curVertex + 3 ) );\r
827                                         curFace++;\r
828                                 }\r
829                                 /* increase vertex count */\r
830                                 curVertex += max;\r
831                         }\r
832                 }\r
833                 /* skip unparsed rest of line and continue */\r
834                 _pico_parse_skip_rest( p );\r
835         }\r
836         /* free memory used by temporary vertexdata */\r
837         FreeObjVertexData( vertexData );\r
838 \r
839         /* return allocated pico model */\r
840         return model;\r
841 //      return NULL;\r
842 }\r
843 \r
844 /* pico file format module definition */\r
845 const picoModule_t picoModuleOBJ =\r
846 {\r
847         "0.6-b",                                        /* module version string */\r
848         "Wavefront ASCII",                      /* module display name */\r
849         "seaw0lf",                                      /* author's name */\r
850         "2002 seaw0lf",                         /* module copyright */\r
851         {\r
852                 "obj",NULL,NULL,NULL    /* default extensions to use */\r
853         },\r
854         _obj_canload,                           /* validation routine */\r
855         _obj_load,                                      /* load routine */\r
856          NULL,                                          /* save validation routine */\r
857          NULL                                           /* save routine */\r
858 };\r