	/* Returns empty scene */
	scene_t		V_GLOBAL sceneCreate();
	/* APIs for adding
typedef struct tdscene_s
{
	struct
	/* geometry info */

/* This structure represent all objects in mesh */
typedef struct	scene_s
{
	u32_t	num_vertices;	/* Number of vertices */
	u32_t	vertex_count;	/* Number of added vertices */
	u32_t				num_texcoords;	/* Number of texture coords */
	u32_t				texcoord_count;	/* Number of added texcoords */
	u32_t				num_normals;	/* Number of normals */
	u32_t				normal_count;	/* Number of added normals */
	u32_t				num_faces;		/* Number of faces */
	u32_t				face_count;		/* Number of added faces */

TODO

/*<html><pre>*/
#include <v_lib.h>
#include <../source/TAL/objbase.h>
#include <../source/geom/mesh.h>

/*
 *	Creates mesh object, copies coords. Assumes all args valid.
 */
V_LOCAL mesh_stroke_p
meshCreateStroke(vec3_t *coords,u32_t num_coords)
{
	mesh_stroke_p stroke;
	u32_t coord_len=num_coords*sizeof(vec3_t);
	stroke=(mesh_stroke_p)memAlloc(sizeof(u32_t)+coord_len);
	stroke->num_coords=num_coords;
	memCopy(stroke->coords,coords,coord_len);
	return stroke;
}

typedef void_p	libv_handle_t;

/*
 *	This object represent series of linestrips (polylines).
 */
typedef struct mesh_s
{
#ifdef	CHECKED_BUILD
	u32_t		magic;
#endif	/* CHECKED_BUILD */
	vp_stack_t	objects;	/* Complete objects */
}	mesh_t, *mesh_p;

#ifdef	CHECKED_BUILD
#define	MESH_MAGIC		0x53415252
V_LOCAL char			*mesh_object="mesh";
#endif	/* CHECKED_BUILD */

libv_handle_t	V_GLOBAL
meshCreate(void)
{
	mesh_t *m;
	ALLOC_OBJECT(m,mesh_t,0,SARRAY_MAGIC);
	vpsInit(&sa->objects);
	return (libv_handle_t)sa;
}

void	V_GLOBAL
meshDestroy(libv_handle_t handle)
{
	void *s, *g;
	mesh_t *m=(mest_t *)
		libvIsHandle(ms,"meshDestroy",mesh_object,MESH_MAGIC);
	if(!sa)
		return;
	/* Clear coords */
	stackClear(&sa->coords);
	/* Delete all strokes */
	while(vpsPop(&sa->strips,&s))
		memFree(s);
	vpsClear(&sa->strips);
	/* Delete all glyphs */
	while(vpsPop(&sa->glyphs,&g))
	{
		while(vpsPop((vp_stack_t *)g,&s))
			memFree(s);
		vpsClear((vp_stack_t *)g);
		memFree(g);
	}
	vpsClear(&sa->glyphs);
	memFree(sa);
}

void	V_GLOBAL
meshStrokeCoord(mesh_strokes_t ms,vec_t x,vec_t y,vec_t z)
{
	vec3_t v;
	vec3Set(v,x,y,z);
	meshStrokeCoordv(ms,v);
}

void	V_GLOBAL
meshStrokeCoordv(mesh_strokes_t ms,vec3_t xyz)
{
	strokes_array_t *sa=(strokes_array_t*)
		libvIsHandle(ms,"meshStrokeCoordv",sarray_object,SARRAY_MAGIC);
	if(!sa)
		return;
	stackPush(&sa->coords,xyz);
}

void	V_GLOBAL
meshCancelStroke(mesh_strokes_t ms)
{
	strokes_array_t *sa=(strokes_array_t*)
		libvIsHandle(ms,"meshCancelStroke",sarray_object,SARRAY_MAGIC);
	if(!sa)
		return;
	/* Clear coords */
	stackResize(&sa->coords,0);
}

void	V_GLOBAL
meshCancelGlyph(mesh_strokes_t ms)
{
	void *s;
	strokes_array_t *sa=(strokes_array_t*)
		libvIsHandle(ms,"meshCancelStroke",sarray_object,SARRAY_MAGIC);
	if(!sa)
		return;
	/* Delete all strokes for new glyph */
	while(vpsPop(&sa->strips,&s))
		memFree(s);
	/* Clear coords */
	stackResize(&sa->coords,0);
}

/*
 *	Stores recorded stroke in array
 */
void	V_GLOBAL
meshEndStroke(mesh_strokes_t ms)
{
	u32_t count;
	vec3_t *coords;
	mesh_stroke_t *stroke;
	strokes_array_t *sa=(strokes_array_t *)
		libvIsHandle(ms,"meshEndStroke",sarray_object,SARRAY_MAGIC);
	if(!sa)
		return;
	count=stackCount(&sa->coords);
	if(count>1)
	{
		coords=(vec3_t*)stackGetStorage(&sa->coords);
		stroke=meshCreateStroke(coords,count);
		if(stroke)
			vpsPush(&sa->strips,stroke);
#ifdef	CHECKED_BUILD
		else
			conlPrintf(LOG_ERROR,"meshEndStroke(): "
				"meshCreateStroke(%X,%u) failed.\n",
				coords,count);
#endif	/* CHECKED_BUILD */
	}
#if	0
	/* BGI fonts contains MoveTo just before end. */
	else
	{
		conlPuts(LOG_WARNING,"meshEndStroke(): stroke must contain "
			"at least 2 coords.\n");
	}
#endif	/* 0 */
	stackResize(&sa->coords,0);
	return;
}

/*
 *	Stores recorded array of strokes in array of glyphs
 */
void	V_GLOBAL
meshEndGlyph(mesh_strokes_t ms)
{
	u32_t count;
	vp_stack_t *glyph;
	strokes_array_t *sa=(strokes_array_t *)
		libvIsHandle(ms,"meshEndGlyph",sarray_object,SARRAY_MAGIC);
	if(!sa)
		return;
	count=stackCount(&sa->strips);
	/* Duplicate stack */
	glyph=(vp_stack_t*)memAlloc(sizeof(vp_stack_t));
	vpsInit(glyph);
	/* Note: it's Ok to create glyph without any strokes. */
	if(count)
	{
		vpsConcat(glyph,&sa->strips);
		vpsTrim(glyph);
	}
	vpsPush(&sa->glyphs,glyph);
	stackResize(&sa->coords,0);
	vpsResize(&sa->strips,0);
	return;
}

u32_t	V_GLOBAL
meshGlyphCount(mesh_strokes_t ms)
{
	strokes_array_t *sa;
	sa=(strokes_array_t *)
		libvIsHandle(ms,"meshGlyphCount",sarray_object,SARRAY_MAGIC);
	if(!sa)
		return 0;
	return vpsCount(&sa->glyphs);
}

u32_t	V_GLOBAL
meshStrokesCount(mesh_strokes_t ms,u32_t glyph_num)
{
	strokes_array_t *sa;
	sa=(strokes_array_t *)
		libvIsHandle(ms,"meshStrokesCount",sarray_object,SARRAY_MAGIC);
	if(!sa)
		return 0;
	if(glyph_num>=vpsCount(&sa->glyphs))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"meshStrokesCount(%X,%u): no such glyph.\n",
			ms,glyph_num);
#endif	/* CHECKED_BUILD */
		return 0;
	}
	return vpsCount((vp_stack_t*)vpsGet(&sa->glyphs,glyph_num));
}

mesh_stroke_p	V_GLOBAL
meshGlyphStroke(mesh_strokes_t ms,u32_t glyph_num,u32_t stroke_num)
{
	u32_t num_glyphs;
	vp_stack_t *glyph;
	strokes_array_t *sa;
	sa=(strokes_array_t *)
		libvIsHandle(ms,"meshGlyphStroke",sarray_object,SARRAY_MAGIC);
	if(!sa)
		return NULL;
	if(glyph_num>=vpsCount(&sa->glyphs))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"meshGlyphStroke(%X,%u,%u): no such glyph.\n",
			ms,glyph_num,stroke_num);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	glyph=(vp_stack_t*)vpsGet(&sa->glyphs,glyph_num);
	if(stroke_num>=vpsCount(glyph))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"meshGlyphStroke(%X,%u,%u): no such stroke.\n",
			ms,glyph_num,stroke_num);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	return vpsGet(glyph,stroke_num);
}


/*
 *	Returns memAlloc()'ed block.
 */
mesh_p	V_GLOBAL
meshAlloc(
	u32_t num_vertices,
	u32_t num_texcoords,
	u32_t num_normals,
	u32_t num_faces,
	u32_t num_objects,
	u32_t num_colors,
	u32_t num_maps,
	u32_t num_mtls)
{
	size_t size;
	mesh_t *obj;
	size=sizeof(mesh_t)+
		num_vertices*sizeof(vec3_t)+
		num_texcoords*sizeof(vec2_t)+
		num_normals*sizeof(vec3_t)+
		num_faces*sizeof(mesh_face_t)+
		num_faces*sizeof(vec3_t)+
		num_faces*sizeof(plane3_t)+
		num_objects*sizeof(mesh_object_t)+
		num_colors*sizeof(color4b_t)+
		num_maps*sizeof(mesh_map_t)+
		num_mtls*sizeof(mesh_mtl_t);
	obj=(mesh_t*)memAlloc(size);
	if(!obj)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"meshAlloc(): can't allocate %d bytes.\n",
			size);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	memZero(obj,size);
	obj->num_vertices=num_vertices;
	obj->num_texcoords=num_texcoords;
	obj->num_normals=num_normals;
	obj->num_faces=num_faces;
	obj->num_objects=num_objects;
	obj->num_colors=num_colors;
	obj->num_maps=num_maps;
	obj->num_mtls=num_mtls;
	obj->vertices=(vec3_t*)(obj+1);
	obj->texcoords=(vec2_t*)(obj->vertices+num_vertices);
	obj->normals=(vec3_t*)(obj->texcoords+num_texcoords);
	obj->faces=(mesh_face_t*)(obj->normals+num_normals);
	obj->face_centers=(vec3_t*)(obj->faces+num_faces);
	obj->face_planes=(plane3_t*)(obj->face_centers+num_faces);
	obj->objects=(mesh_object_t*)(obj->face_planes+num_faces);
	obj->colors=(color4b_t*)(obj->objects+num_objects);
	obj->maps=(mesh_map_t*)(obj->colors+num_colors);
	obj->mtls=(mesh_mtl_t*)(obj->maps+num_maps);
	return obj;
}

/*
 *	Sets all counters to zero, but NOT frees any memory.
 */
void	V_GLOBAL
meshClear(mesh_t *mesh)
{
	V_ASSERT(mesh);
	mesh->vertex_count=0;
	mesh->texcoord_count=0;
	mesh->normal_count=0;
	mesh->color_count=0;
	mesh->face_count=0;
	mesh->object_count=0;
	mesh->color_count=0;
	mesh->map_count=0;
	mesh->mtl_count=0;
}

/*
 *	Adding procedures.
 */
void	V_GLOBAL
meshAddVertex(mesh_t *mesh,vec3_t vert)
{
	V_ASSERT(mesh && mesh->num_vertices>mesh->vertex_count);
	vec3Copy(mesh->vertices[mesh->vertex_count++],vert);
}

void	V_GLOBAL
meshAddTexcoord(mesh_t *mesh,vec2_t texcoord)
{
	V_ASSERT(mesh && mesh->num_texcoords>mesh->texcoord_count);
	vec2Copy(mesh->texcoords[mesh->texcoord_count++],texcoord);
}

void	V_GLOBAL
meshAddNormal(mesh_t *mesh,vec3_t normal)
{
	V_ASSERT(mesh && mesh->num_normals>mesh->normal_count);
	vec3Copy(mesh->normals[mesh->normal_count++],normal);
}

void	V_GLOBAL
meshAddColor(mesh_t *mesh,color4b_t color)
{
	V_ASSERT(mesh && mesh->num_colors>mesh->color_count);
	rgbaByteCopy(mesh->colors[mesh->color_count++],color);
}

void	V_GLOBAL
meshAddObject(mesh_t *mesh,mesh_object_t *object)
{
	V_ASSERT(mesh && mesh->num_objects>mesh->object_count);
	memCopy(&mesh->objects[mesh->object_count++],object,sizeof(mesh_object_t));
}

void	V_GLOBAL
meshAddFace(mesh_t *mesh,mesh_face_t *face)
{
	V_ASSERT(mesh && mesh->num_faces>mesh->face_count);
	/* Note: mesh_face_t is an array, so we should avoid 
	 * references such &mesh->[i] */
	memCopy(mesh->faces + mesh->face_count++,face,sizeof(mesh_face_t));
}

void	V_GLOBAL
meshAddMap(mesh_t *mesh,mesh_map_t *map)
{
	V_ASSERT(mesh && mesh->num_maps>mesh->map_count);
	memCopy(&mesh->maps[mesh->map_count++],map,sizeof(mesh_map_t));
}

void	V_GLOBAL
meshAddMtl(mesh_t *mesh,mesh_mtl_t *mtl)
{
	V_ASSERT(mesh && mesh->num_mtls>mesh->mtl_count);
	memCopy(&mesh->mtls[mesh->mtl_count++],mtl,sizeof(mesh_mtl_t));
}

/*
 *	Saves mesh to Wavefront OBJ file.
 */
vboolean	V_GLOBAL
objSave(char *filename,mesh_t *obj)
{
	u32_t i, j, fc;
	vhandle_t out;
	if(!filename || !obj)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"objSave(%s,%X): invalid parameters.\n",
			filename ? filename : "null", obj);
#endif	/* CHECKED_BUILD */
		return false;
	}
	out=fileOpenWrite(filename);
	if(!out)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"objSave(%s): can't create file.\n",filename);
#endif	/* CHECKED_BUILD */
		return false;
	}
	filePrintf(out,"# %d vertices, %d texcoords, %d normals, "
		"%d faces, %d objects\n",obj->vertex_count,obj->texcoord_count,
		obj->normal_count,obj->face_count,obj->object_count);
	filePrintf(out,"# Scene bounding box [%f,%f,%f]-[%f,%f,%f]\n",
		obj->mins[0],obj->mins[1],obj->mins[2],
		obj->maxs[0],obj->maxs[1],obj->maxs[2]);
	/* Print all vertices */
	filePrintf(out,"# Vertex list\n");
	for(i=0;i<obj->vertex_count;i++)
		filePrintf(out,"v %g %g %g\n",obj->vertices[i][0],
			obj->vertices[i][1],obj->vertices[i][2]);
	/* Print all texture coords */
	filePrintf(out,"# Texture coord list\n");
	for(i=0;i<obj->texcoord_count;i++)
		filePrintf(out,"vt %g %g 0\n",obj->texcoords[i][0],
			obj->texcoords[i][1]);
	/* Print all normals */
	filePrintf(out,"# Normal list\n");
	for(i=0;i<obj->normal_count;i++)
		filePrintf(out,"vn %g %g %g\n",obj->normals[i][0],
			obj->normals[i][1],obj->normals[i][2]);
	/* Print all objects */
	filePrintf(out,"# Object list\n");
	for(i=fc=0;i<obj->object_count;i++)
	{
		filePrintf(out,"# Bounding box of object %d: [%g,%g,%g]-[%g,%g,%g]\n",
			i,
			obj->objects[i].mins[0],
			obj->objects[i].mins[1],
			obj->objects[i].mins[2],
			obj->objects[i].maxs[0],
			obj->objects[i].maxs[1],
			obj->objects[i].maxs[2]);
		filePrintf(out,"# Pivot point of object %d: [%g,%g,%g]\n",
			i,
			obj->objects[i].pivot[0],
			obj->objects[i].pivot[1],
			obj->objects[i].pivot[2]);
		filePrintf(out,"usemtl %s\n",obj->objects[i].material);
		filePrintf(out,"o %s\n",obj->objects[i].name);
		/* Print object faces */
		if(obj->objects[i].faces_flipped)
		{
			for(j=0;j<obj->objects[i].num_faces;j++,fc++)
				filePrintf(out,"f %d/%d/%d %d/%d/%d %d/%d/%d\n",
					obj->faces[fc][0].vertex+1,
					obj->faces[fc][0].texcoord+1,
					obj->faces[fc][0].normal+1,
	
					obj->faces[fc][2].vertex+1,
					obj->faces[fc][2].texcoord+1,
					obj->faces[fc][2].normal+1,

					obj->faces[fc][1].vertex+1,
					obj->faces[fc][1].texcoord+1,
					obj->faces[fc][1].normal+1
				);
		}
		else
		{
			for(j=0;j<obj->objects[i].num_faces;j++,fc++)
				filePrintf(out,"f %d/%d/%d %d/%d/%d %d/%d/%d\n",
					obj->faces[fc][0].vertex+1,
					obj->faces[fc][0].texcoord+1,
					obj->faces[fc][0].normal+1,
	
					obj->faces[fc][1].vertex+1,
					obj->faces[fc][1].texcoord+1,
					obj->faces[fc][1].normal+1,
	
					obj->faces[fc][2].vertex+1,
					obj->faces[fc][2].texcoord+1,
					obj->faces[fc][2].normal+1
				);
		}
		filePrintf(out,"# %d triangles\n",obj->objects[i].num_faces);
	}
	fileClose(out);
	return true;
}

/*
 *	This function counts all OBJ mesh components.
 */
void V_LOCAL
count_obj_components(vhandle_t obj,u32_t *vertex_c,u32_t *texcoord_c,
	u32_t *normal_c,u32_t *face_c,u32_t *object_c)
{
	char *str;
	u32_t nv,nt,nn,nf,no;
	lexPush(obj);
	nv=nt=nn=nf=no=0;
	while((str=tokString(obj))!=NULL)
	{
		if(str[0]=='v' && str[1]==32)
			nv++;
		else if(str[0]=='v' && str[1]=='t' && str[2]==32)
			nt++;
		else if(str[0]=='v' && str[1]=='n' && str[2]==32)
			nn++;
		else if(str[0]=='f' && str[1]==32)
			nf++;
		else if(str[0]=='o' && str[1]==32)
			no++;
	}
	if(vertex_c)
		*vertex_c=nv;
	if(texcoord_c)
		*texcoord_c=nt;
	if(normal_c)
		*normal_c=nn;
	if(face_c)
		*face_c=nf;
	if(object_c)
		*object_c=no;
	lexPop(obj);
}

mesh_p V_LOCAL
alloc_obj_mesh(vhandle_t obj,char *filename)
{
	u32_t nv,nt,nn,nf,no;
	mesh_t *mesh;
	count_obj_components(obj,&nv,&nt,&nn,&nf,&no);
	if(!nv || !no || !nf)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"alloc_obj_mesh(%s): emply mesh.\n",filename);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	if(!nt)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,"alloc_obj_mesh(%s): no texture coords.\n",filename);
#endif	/* CHECKED_BUILD */
		nt=1;
	}
	if(!nn)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,"alloc_obj_mesh(%s): no normals.\n",filename);
#endif	/* CHECKED_BUILD */
		nn=1;
	}
	mesh=meshAlloc(nv,nt,nn,nf,no,1,1,1);
	mesh->vertex_count=nv;
	mesh->texcoord_count=nt;
	mesh->normal_count=nn;
	mesh->face_count=nf;
	mesh->object_count=no;
	mesh->color_count=1;
	mesh->colors[0][0]=200;
	mesh->colors[0][1]=200;
	mesh->colors[0][2]=200;
	mesh->colors[0][3]=128;
	return mesh;
}

/*
 *	Misc stuff.
 */
void V_LOCAL
obj_cleanup_name(char *name)
{
	int i,len;
	name[63]=0;
	len=strlen(name);
	for(i=len-1;i>=0;i--)
	{
		if(name[i]<32)
			name[i]=0;
	}
	for(i=len;i<64;i++)
		name[i]=0;
}

void V_LOCAL
calculate_mesh_bounding_boxes(mesh_t *obj)
{
	u32_t i,j;
	u32_t sf, f_e;
	/* Calculate bounding boxes for all objects */
	for(i=0;i<obj->num_objects;i++)
	{
		bboxClear(obj->objects[i].mins,obj->objects[i].maxs);
		sf = obj->objects[i].startface;
		f_e = sf + obj->objects[i].num_faces;
		for(j=sf;j<f_e;j++)
		{
			bboxAddPoint(obj->objects[i].mins,
				obj->objects[i].maxs,
				obj->vertices[obj->faces[j][0].vertex]);
			bboxAddPoint(obj->objects[i].mins,
				obj->objects[i].maxs,
				obj->vertices[obj->faces[j][1].vertex]);
			bboxAddPoint(obj->objects[i].mins,
				obj->objects[i].maxs,
				obj->vertices[obj->faces[j][2].vertex]);
		}
		/* Set pivot to center of object */
		vec3Add(obj->objects[i].pivot,obj->objects[i].mins,obj->objects[i].maxs);
		vec3Scale(obj->objects[i].pivot,obj->objects[i].pivot,0.5);
	}
	/* Calculate bounding box for all geometry */
	bboxClear(obj->mins,obj->maxs);
	for(i=0;i<obj->num_objects;i++)
	{
		bboxAddPoint(obj->mins,obj->maxs,obj->objects[i].mins);
		bboxAddPoint(obj->mins,obj->maxs,obj->objects[i].maxs);
	}
}

void V_LOCAL
calculate_mesh_face_parameters(mesh_t *obj)
{
	int i,num;
	vec3_t v1,v2,v3,c;
	num=obj->num_faces;
	for(i=0;i<num;i++)
	{
		vec3Copy(v1,obj->vertices[obj->faces[i][0].vertex]);
		vec3Copy(v2,obj->vertices[obj->faces[i][1].vertex]);
		vec3Copy(v3,obj->vertices[obj->faces[i][2].vertex]);
		/* Calculate center of face */
		vec3Add(c,v1,v2);
		vec3Add(c,c,v3);
		vec3Scale(c,c,(vec_t)0.333333333);
		vec3Copy(obj->face_centers[i],c);
		/* Calculate plane equation */
		plane3FromPoints(obj->face_planes+i,v1,v2,v3);
	}
}

int V_LOCAL
parse_vec3(char *str,vec_t *v)
{
	char f_buf[3][_MAX_PATH];
	int retval;
	retval=sscanf(str,"%s %s %s",f_buf[0],f_buf[1],f_buf[2]);
	if(retval!=3)
	{
		return retval;
	}
	v[0]=(vec_t)atof(f_buf[0]);
	v[1]=(vec_t)atof(f_buf[1]);
	v[2]=(vec_t)atof(f_buf[2]);
	return 3;
}

int	V_LOCAL
parse_indices(char *src,int vtn[],unsigned char mask[],int max_entries)
{
	int ind=0;
	char *ptr=src;
	char old;
	for(;ind<max_entries;ptr++)
	{
		if(*ptr=='/' || !*ptr)
		{
			if(ptr>src)
			{
				old=*ptr;
				*ptr=0;
				if(vtn)
					vtn[ind]=atoi(src);
				mask[ind]=1;
				*ptr=old;
			}
			else
			{
				vtn[ind]=0;
				mask[ind]=0;
			}
			ind++;
			src=ptr+1;
			if(!*ptr)
				break;
		}
	}
	return ind;
}

/* returns number of vertices == 3 */
vboolean	V_LOCAL
parse_triangle(mesh_t *dest,char *str,int cf)
{
	/* ERROR: this code BROKEN !!! */
	int	vert;
	int vtn[3];
	u8_t mask[3];
	char *ptr;
	char old;
	strStripLT(str);
	for(vert=0;vert<3;ptr++)
	{
		/* delimit next token */
		for(ptr=str;*ptr && *ptr!=' ';ptr++);
		/* if token has non-nul length */
		if(ptr>str)
		{
			/* parse it */
			old=*ptr;
			*ptr=0;
			parse_indices(str,vtn,mask,3);
			dest->faces[cf][vert].vertex = mask[0] ? vtn[0]-1 : 0;
			dest->faces[cf][vert].texcoord = mask[1] ? vtn[1]-1 : 0;
			dest->faces[cf][vert].normal = mask[2] ? vtn[2]-1 : 0;
			*ptr=old;
			vert++;
		}
		else
			return vert==3;
		ptr++;
		str=ptr;
	}
	return true;
}

/*
 *	Loads mesh from Wavefront OBJ file.
 */
mesh_p V_GLOBAL
objLoad(char *filename)
{
	mesh_t *obj;
	u32_t cv, ct, cn, cf, count;
	int co;
	char *str;
	char last_mtl[64];
	vhandle_t in;
	vec3_t v3;
	if(!filename)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"objLoad('%s'): invalid file name.\n",
			strSafe(NULL));
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	in=lexParseFile(filename,"\n\r");
	if(!in)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"objLoad(%s): can't open.\n",filename);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	obj=alloc_obj_mesh(in,filename);
	if(!obj)
	{
		/* Stay quiet, alloc_obj_mesh() already reported */
		return NULL;
	}
	/*
	 *		Load mesh data. Keep value of 'usemtl' because new object starts
	 *	only when 'o' found, which is after 'usemtl'.
	 */
	cv=ct=cn=cf=0;
	co=-1;
	strcpy(last_mtl,"default");
	for(;;)
	{
		str=tokString(in);
		if(!str)
			break;
		if(str[0]=='#') /* comment */
			continue;
		else if(str[0]=='v' && str[1]==32)
		{
			/* vertex */
			parse_vec3(&str[2],obj->vertices[cv]);
			cv++;
		} else if(str[0]=='v' && str[1]=='t' && str[2]==32)
		{
			/*	Texcoord have 3 entries; we use only 2. */
			parse_vec3(&str[2],v3);
			obj->texcoords[ct][0]=v3[0];
			obj->texcoords[ct][1]=v3[1];
			ct++;
		} else if(str[0]=='v' && str[1]=='n' && str[2]==32)
		{
			/* normal */
			parse_vec3(&str[2],obj->normals[cn]);
			cn++;
		} else if(str[0]=='f' && str[1]==32)
		{
			/* face */
			if(!parse_triangle(obj,str+2,cf))
			{
#ifdef			CHECKED_BUILD
				conlPrintf(LOG_ERROR,
					"objLoad('%s'): can't parse face %d (object '%s').\n",
					filename, cf,
					(co==-1) ? "unknown" : obj->objects[co].name);
#endif			/* CHECKED_BUILD */
				memFree(in);
				memFree(obj);
				return NULL;
			}
			if(co==-1)
			{
#ifdef			CHECKED_BUILD
				conlPrintf(LOG_WARNING,
					"objLoad(%s): isolated face %d ignored.\n",
					filename,cf);
#endif			/* CHECKED_BUILD */
			} else
				obj->objects[co].num_faces++;
			cf++;
		} else if(!memcmp(str,"usemtl ",7))
		{
			/* material name */
			strncpy(last_mtl,str+7,64);
			obj_cleanup_name(last_mtl);
		} else if(str[0]=='o' && str[1]==32)
		{
			/* object */
			co++;
			strncpy(obj->objects[co].name,str+2,64);
			obj_cleanup_name(obj->objects[co].name);
			strcpy(obj->objects[co].material,last_mtl);
			obj->objects[co].startface=cf;
		}
	}
	calculate_mesh_bounding_boxes(obj);
	calculate_mesh_face_parameters(obj);
	for(count=0;count<obj->num_objects;count++)
		mat3x4Unit(obj->objects[count].xform);
	lexDestroy(in);
	return obj;
}

/*</pre></strong></body></html>*/
/*EOF*/
