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