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