How to Sort the Texture Positions Based on the Texture Indices Given in a Wavefront (.Obj) File

How do I sort the texture positions based on the texture indices given in a Wavefront (.obj) file?

I wanted to implement this (adding textures for obj file) into my engine for a long time and your Question got me the mood to actually do it :).

The image you provided as texture looks more like a preview than a texture. Also the texture coordinates does not correspond to it as you can see in preview:

3D preview

If you look at the texture coordinates:

vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.263898
vt 0.263898 0.736102
vt 0.263898 0.263898
vt 0.736102 0.736102
vt 0.736102 0.736102
vt 0.736102 0.736102
vt 0.736102 0.736102
vt 0.736102 0.736102

there are just 2 numbers:

0.736102
0.263898

Which makes sense for axis aligned quad or square sub-image in the texture which isnot present in your texture. Also the number of texture points makes no sense 20 it should be just 4. Hence the confusions you got.

Anyway Rabbid76 is right you need to duplicate points ... Its relatively easy so:

  1. extract all positions, colors, texture points and normals

    from your obj file into separate tables. So parse lines starting with v,vt,vn and create 4 tables from it. Yes 4 as color is sometimes encoded in v as v x y z r g b as output from some 3D scanners.

    So you should have something like this:

    double ppos[]= // v
    {
    -1.000000, 1.000000, 1.000000,
    -1.000000,-1.000000,-1.000000,
    -1.000000,-1.000000, 1.000000,
    -1.000000, 1.000000,-1.000000,
    1.000000,-1.000000,-1.000000,
    1.000000, 1.000000,-1.000000,
    1.000000,-1.000000, 1.000000,
    1.000000, 1.000000, 1.000000,
    };
    double pcol[]= // v
    {
    };
    double ptxr[]= // vt
    {
    0.736102,0.263898,
    0.263898,0.736102,
    0.263898,0.263898,
    0.736102,0.263898,
    0.263898,0.736102,
    0.263898,0.263898,
    0.736102,0.263898,
    0.263898,0.736102,
    0.263898,0.263898,
    0.736102,0.263898,
    0.263898,0.736102,
    0.263898,0.263898,
    0.736102,0.263898,
    0.263898,0.736102,
    0.263898,0.263898,
    0.736102,0.736102,
    0.736102,0.736102,
    0.736102,0.736102,
    0.736102,0.736102,
    0.736102,0.736102,
    };
    double pnor[]= // vn
    {
    -0.5774, 0.5774, 0.5774,
    -0.5774,-0.5774,-0.5774,
    -0.5774,-0.5774, 0.5774,
    -0.5774, 0.5774,-0.5774,
    0.5774,-0.5774,-0.5774,
    0.5774, 0.5774,-0.5774,
    0.5774,-0.5774, 0.5774,
    0.5774, 0.5774, 0.5774,
    };
  2. process faces f

    now you should handle the above tables as temp data and create real data for your mesh from scratch into new structure (or load it directly to VBOs). So what you need is to reindex all the f data to unique combinations of all the indexes present. To do that you need to keep track what you already got. For that I amusing this structure:

    class vertex
    {
    public:
    int pos,txr,nor;
    vertex(){}; vertex(vertex& a){ *this=a; }; ~vertex(){}; vertex* operator = (const vertex *a) { *this=*a; return this; }; /*vertex* operator = (const vertex &a) { ...copy... return this; };*/
    int operator == (vertex &a) { return (pos==a.pos)&&(txr==a.txr)&&(nor==a.nor); }
    int operator != (vertex &a) { return (pos!=a.pos)||(txr!=a.txr)||(nor!=a.nor); }
    };

    so create empty list of vertex now process first f line and extract indexes

    f 1/1/1 2/2/2 3/3/3

    so for each point (process just one at a time) in the face extract its ppos,ptxr,pnor index. Now check if it is already present in your final mesh data. If yes use its index instead. If not add new point to all tables your mesh have (pos,col,txr,nor) and use index of newly added point.

    When all points of a face was processed add the face with the reindexed indexes into your final mesh faces and process next f line.

Just for sure here is my Wavefront OBJ loader C++ class I am using in my engine (but it depends on the engine itself so you can not use it directly it is just to see the structure of code and how to encode this... as starting with this from scratch might be difficult).

//---------------------------------------------------------------------------
//--- Wavefront obj librrary ver: 2.11 --------------------------------------
//---------------------------------------------------------------------------
#ifndef _model_obj_h
#define _model_obj_h
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
class model_obj
{
public:
class vertex
{
public:
int pos,txr,nor;
vertex(){}; vertex(vertex& a){ *this=a; }; ~vertex(){}; vertex* operator = (const vertex *a) { *this=*a; return this; }; /*vertex* operator = (const vertex &a) { ...copy... return this; };*/
int operator == (vertex &a) { return (pos==a.pos)&&(txr==a.txr)&&(nor==a.nor); }
int operator != (vertex &a) { return (pos!=a.pos)||(txr!=a.txr)||(nor!=a.nor); }
};

OpenGL_VAO obj;

model_obj();
~model_obj();
void reset();

void load(AnsiString name);
int save(OpenGL_VAOs &vaos);
};
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
model_obj::model_obj()
{
reset();
}
//---------------------------------------------------------------------------
model_obj::~model_obj()
{
reset();
}
//---------------------------------------------------------------------------
void model_obj::reset()
{
obj.reset();
}
//---------------------------------------------------------------------------
void model_obj::load(AnsiString name)
{
int adr,siz,hnd;
BYTE *dat;

reset();
siz=0;
hnd=FileOpen(name,fmOpenRead);
if (hnd<0) return;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
dat=new BYTE[siz];
if (dat==NULL) { FileClose(hnd); return; }
FileRead(hnd,dat,siz);
FileClose(hnd);

AnsiString s,s0,t;
int a,i,j;
double alpha=1.0;
List<double> f;
List<int> pos,txr,nor;
List<double> ppos,pcol,pnor,ptxr; // OBJ parsed data
vertex v;
List<vertex> pv;

f.allocate(6);

ppos.num=0;
pcol.num=0;
pnor.num=0;
ptxr.num=0;
obj.reset();
// purpose, location, type,datatype,datacomponents,pack_acc);
obj.addVBO(_OpenGL_VBO_purpose_pos ,vbo_loc_pos , GL_ARRAY_BUFFER,GL_FLOAT, 3, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_col ,vbo_loc_col , GL_ARRAY_BUFFER,GL_FLOAT, 4, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_txr0,vbo_loc_txr0, GL_ARRAY_BUFFER,GL_FLOAT, 2, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_nor ,vbo_loc_nor , GL_ARRAY_BUFFER,GL_FLOAT, 3, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_fac , -1,GL_ELEMENT_ARRAY_BUFFER, GL_INT, 3, 0.0);
obj.draw_mode=GL_TRIANGLES;
obj.rep.reset();
obj.filename=name;

_progress_init(siz); int progress_cnt=0;
for (adr=0;adr<siz;)
{
progress_cnt++; if (progress_cnt>=1024) { progress_cnt=0; _progress(adr); }

s0=txt_load_lin(dat,siz,adr,true);
a=1; s=str_load_str(s0,a,true);

// clear temp vector in case of bug in obj file
f.num=0; for (i=0;i<6;i++) f.dat[i]=0.0;

if (s=="v")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
if (f.num>=3)
{
ppos.add(f[0]);
ppos.add(f[1]);
ppos.add(f[2]);
}
if (f.num==6)
{
pcol.add(f[3]);
pcol.add(f[4]);
pcol.add(f[5]);
}
}
else if (s=="vn")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
pnor.add(f[0]);
pnor.add(f[1]);
pnor.add(f[2]);
}
else if (s=="vt")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
ptxr.add(f[0]);
ptxr.add(f[1]);
}
else if (s=="f")
{
pos.num=0;
txr.num=0;
nor.num=0;
for (;;)
{
s=str_load_str(s0,a,true); if (s=="") break;
for (t="",i=1;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) pos.add(str2int(t)-1);
for (t="",i++;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) txr.add(str2int(t)-1);
for (t="",i++;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) nor.add(str2int(t)-1);
}
// reindex and or duplicate vertexes if needed
for (i=0;i<pos.num;i++)
{
// wanted vertex
v.pos=pos[i];
if (txr.num>0) v.txr=txr[i]; else v.txr=-1;
if (nor.num>0) v.nor=nor[i]; else v.nor=-1;
// is present in VBO?
for (j=0;j<pv.num;j++)
if (v==pv[j])
{ pos[i]=j; j=-1; break; }
// if not add it
if (j>=0)
{
j=v.pos; j=j+j+j; if (pcol.num>0) obj.addpntcol(ppos[j+0],ppos[j+1],ppos[j+2],pcol[j+0],pcol[j+1],pcol[j+2],alpha);
else obj.addpnt (ppos[j+0],ppos[j+1],ppos[j+2]);
j=v.nor; j=j+j+j; if (v.nor>=0) obj.addnor (pnor[j+0],pnor[j+1],pnor[j+2]);
j=v.txr; j=j+j; if (v.txr>=0) obj.addtxr (ptxr[j+0],ptxr[j+1]);
pos[i]=pv.num; pv.add(v);
}
}
for (i=2;i<pos.num;i++) obj.addface(pos[0],pos[i-1],pos[i]);
}
}
_progress_done();
delete[] dat;

}
//---------------------------------------------------------------------------
int model_obj::save(OpenGL_VAOs &vaos)
{
int vaoix0=-1;
OpenGL_VBO *vn=obj.getVBO(_OpenGL_VBO_purpose_nor );
if (vn->data.num==0) obj.nor_compute();
vaos.vao=obj;
vaoix0=vaos.add(obj);
return vaoix0;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------

It does not use the *.mtl file yet (I hardcoded the texture for the preview).

PS. if I use this as texture:

texture

The result looks like this:

preview

I use a lot of mine own stuff here so some explanations:


str_load_str(s,i,true) returns string representing first valid word from index i in string s. The true means just that i is updated with new position in s.

str_load_lin(s,i,true) returns string representing line (till CR or LF or CRLF or LFCR) from index i in string s. The true means just that i is updated with new position after that line.

txt_load_... is the same but instead of reading from string it reads form BYTE* or CHAR* if you want.

Beware AnsiString is indexed form 1 and BYTE*,CHAR* from 0.

I also use mine dynamic list template so:


List<double> xxx; is the same as double xxx[];
xxx.add(5); adds 5 to end of the list

xxx[7] access array element (safe)

xxx.dat[7] access array element (unsafe but fast direct access)

xxx.num is the actual used size of the array

xxx.reset() clears the array and set xxx.num=0
xxx.allocate(100) preallocate space for 100 items

Here the updated faster reindex code with textures from mtl file (otherstuff is ignored and only single object/texture is supported for now):

//---------------------------------------------------------------------------
//--- Wavefront obj librrary ver: 2.11 --------------------------------------
//---------------------------------------------------------------------------
#ifndef _model_obj_h
#define _model_obj_h
//---------------------------------------------------------------------------
class model_obj
{
public:

class vertex
{
public:
int pos,txr,nor;
vertex(){}; vertex(vertex& a){ *this=a; }; ~vertex(){}; vertex* operator = (const vertex *a) { *this=*a; return this; }; /*vertex* operator = (const vertex &a) { ...copy... return this; };*/
int operator == (vertex &a) { return (pos==a.pos)&&(txr==a.txr)&&(nor==a.nor); }
int operator != (vertex &a) { return (pos!=a.pos)||(txr!=a.txr)||(nor!=a.nor); }
int operator < (vertex &a)
{
if (pos>a.pos) return 0;
if (pos<a.pos) return 1;
if (txr>a.txr) return 0;
if (txr<a.txr) return 1;
if (nor<a.nor) return 1;
return 0;
}
void ld(int p,int t,int n) { pos=p; txr=t; nor=n; }
};

class vertexes
{
public:
List<vertex> pv; // vertexes in order
List<int> ix; // inex sort ASC for faster access
int m; // power of 2 >= ix.num
vertexes(){}; vertexes(vertexes& a){ *this=a; }; ~vertexes(){}; vertexes* operator = (const vertexes *a) { *this=*a; return this; }; /*vertexes* operator = (const vertexes &a) { ...copy... return this; };*/
void reset() { m=0; pv.num=0; ix.num=0; }
bool get(int &idx,vertex &v) // find idx so pv[idx]<=v and return if new vertex was added
{
int i,j;
// handle first point
if (ix.num<=0)
{
m=1;
idx=0;
pv.add(v);
ix.add(0);
return true;
}
// bin search closest idx
for (j=0,i=m;i;i>>=1)
{
j|=i;
if (j>=ix.num) { j^=i; continue; }
if (v<pv.dat[ix.dat[j]]) j^=i;
}
// stop if match found
idx=ix.dat[j];
if (v==pv.dat[idx]) return false;
// add new index,vertex if not
idx=pv.num; pv.add(v); j++;
if (j>=ix.num) ix.add(idx);
else ix.ins(j,idx);
if (ix.num>=m+m) m<<=1;
return true;
}
};

struct material
{
AnsiString nam,txr;
material(){}; material(material& a){ *this=a; }; ~material(){}; material* operator = (const material *a) { *this=*a; return this; }; /*material* operator = (const material &a) { ...copy... return this; };*/
};

List<material> mat;
OpenGL_VAO obj;

model_obj();
~model_obj();
void reset();

void load(AnsiString name);
int save(OpenGL_VAOs &vaos);
};
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
model_obj::model_obj()
{
reset();
}
//---------------------------------------------------------------------------
model_obj::~model_obj()
{
reset();
}
//---------------------------------------------------------------------------
void model_obj::reset()
{
obj.reset();
mat.reset();
}
//---------------------------------------------------------------------------
void model_obj::load(AnsiString name)
{
AnsiString path=ExtractFilePath(name);
int adr,siz,hnd;
BYTE *dat;

reset();
siz=0;
hnd=FileOpen(name,fmOpenRead);
if (hnd<0) return;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
dat=new BYTE[siz];
if (dat==NULL) { FileClose(hnd); return; }
FileRead(hnd,dat,siz);
FileClose(hnd);

AnsiString s,s0,t;
int a,i,j;
double alpha=1.0;
List<double> f;
List<int> pos,txr,nor;
List<double> ppos,pcol,pnor,ptxr; // OBJ parsed data
vertex v;
vertexes pver;
material m0,*m=NULL;

f.allocate(6);
pver.reset();

ppos.num=0;
pcol.num=0;
pnor.num=0;
ptxr.num=0;
obj.reset();
// purpose, location, type,datatype,datacomponents,pack_acc);
obj.addVBO(_OpenGL_VBO_purpose_pos ,vbo_loc_pos , GL_ARRAY_BUFFER,GL_FLOAT, 3, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_col ,vbo_loc_col , GL_ARRAY_BUFFER,GL_FLOAT, 4, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_txr0,vbo_loc_txr0, GL_ARRAY_BUFFER,GL_FLOAT, 2, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_nor ,vbo_loc_nor , GL_ARRAY_BUFFER,GL_FLOAT, 3, 0.0001);
obj.addVBO(_OpenGL_VBO_purpose_fac , -1,GL_ELEMENT_ARRAY_BUFFER, GL_INT, 3, 0.0);
obj.draw_mode=GL_TRIANGLES;
obj.rep.reset();
obj.filename=name;

_progress_init(siz); int progress_cnt=0;
for (adr=0;adr<siz;)
{
progress_cnt++; if (progress_cnt>=1024) { progress_cnt=0; _progress(adr); }

s0=txt_load_lin(dat,siz,adr,true);
a=1; s=str_load_str(s0,a,true);

// clear temp vector in case of bug in obj file
f.num=0; for (i=0;i<6;i++) f.dat[i]=0.0;

if (s=="v")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
if (f.num>=3)
{
ppos.add(f[0]);
ppos.add(f[1]);
ppos.add(f[2]);
}
if (f.num==6)
{
pcol.add(f[3]);
pcol.add(f[4]);
pcol.add(f[5]);
}
}
else if (s=="vn")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
pnor.add(f[0]);
pnor.add(f[1]);
pnor.add(f[2]);
}
else if (s=="vt")
{
f.num=0;
for (;;)
{
s=str_load_str(s0,a,true);
if ((s=="")||(!str_is_num(s))) break;
f.add(str2num(s));
}
ptxr.add(f[0]);
ptxr.add(f[1]);
}
else if (s=="f")
{
pos.num=0;
txr.num=0;
nor.num=0;
for (;;)
{
s=str_load_str(s0,a,true); if (s=="") break;
for (t="",i=1;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) pos.add(str2int(t)-1);
for (t="",i++;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) txr.add(str2int(t)-1);
for (t="",i++;i<=s.Length();i++) if (s[i]=='/') break; else t+=s[i]; if ((t!="")&&(str_is_num(t))) nor.add(str2int(t)-1);
}
// reindex and or duplicate vertexes if needed
for (i=0;i<pos.num;i++)
{
// wanted vertex
v.pos=pos[i];
if (txr.num>0) v.txr=txr[i]; else v.txr=-1;
if (nor.num>0) v.nor=nor[i]; else v.nor=-1;
if (pver.get(pos[i],v)) // is present in VBO? if not add it
{
j=v.pos; j=j+j+j; if (pcol.num>0) obj.addpntcol(ppos[j+0],ppos[j+1],ppos[j+2],pcol[j+0],pcol[j+1],pcol[j+2],alpha);
else obj.addpnt (ppos[j+0],ppos[j+1],ppos[j+2]);
j=v.nor; j=j+j+j; if (v.nor>=0) obj.addnor (pnor[j+0],pnor[j+1],pnor[j+2]);
j=v.txr; j=j+j; if (v.txr>=0) obj.addtxr (ptxr[j+0],ptxr[j+1]);
}
}
for (i=2;i<pos.num;i++) obj.addface(pos[0],pos[i-1],pos[i]);
}
else if (s=="mtllib")
{
AnsiString s1;
int adr,siz,hnd;
BYTE *dat;
// extract mtl filename
s=str_load_str(s0,a,true);
s+=str_load_lin(s0,a,true);
// load it to memory
siz=0;
hnd=FileOpen(path+s,fmOpenRead);
if (hnd<0) continue;
siz=FileSeek(hnd,0,2);
FileSeek(hnd,0,0);
dat=new BYTE[siz];
if (dat==NULL) { FileClose(hnd); continue; }
FileRead(hnd,dat,siz);
FileClose(hnd);
// extract textures and stuff
m=&m0;
for (adr=0;adr<siz;)
{
s1=txt_load_lin(dat,siz,adr,true);
a=1; s=str_load_str(s1,a,true);
if (s=="newmtl")
{
s=str_load_str(s1,a,true);
s+=str_load_lin(s1,a,true);
mat.add();
m=&mat[mat.num-1];
m->nam=s;
m->txr="";
}
else if (s=="map_Kd")
{
s=str_load_str(s1,a,true);
s+=str_load_lin(s1,a,true);
m->txr=s;
}
}
delete[] dat;
m=NULL;
}
else if (s=="usemtl")
{
// extract material name
s=str_load_str(s0,a,true);
s+=str_load_lin(s0,a,true);
// find it in table
for (m=mat.dat,i=0;i<mat.num;i++,m++)
if (m->nam==s) { i=-1; break; }
if (i>=0) m=NULL;
}
}
// textures
for (i=0;i<mat.num;i++)
if (mat[i].txr!="")
{
OpenGL_VAO::_TXR txr;
txr.ix=-1;
txr.unit=txr_unit_map;
txr.filename=mat[i].txr;
txr.txrtype=GL_TEXTURE_2D;
txr.repeat=GL_REPEAT;
obj.txr.add(txr);
}

_progress_done();
delete[] dat;
}
//---------------------------------------------------------------------------
int model_obj::save(OpenGL_VAOs &vaos)
{
int vaoix0=-1,i;
OpenGL_VBO *vn=obj.getVBO(_OpenGL_VBO_purpose_nor );
if (vn) if (vn->data.num==0) obj.nor_compute();
vaos.vao=obj;

vaoix0=vaos.add(obj);
return vaoix0;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------

Appart the added materials (just texture and material name for now) I changed the re-indexing so the vertextes are index sorted and binary search is used to obtain vertex index on demand. With this 100K faces Standford dragon (3.4MByte) is loaded in 3.7sec:

dragon

Extracting face vertex indices from a Wavefront .obj file

A brief overview

The code below is by no means a complete Wavefront OBJ parser, but it satisfies the requirements in the question. First, it checks if the first character of the line is an 'f', if this is not the case, then we can skip this line. Else, the line parsing begins.

We first skip past the 'f' and then repeat two calls to strtok with alternating delimiters. In the first one, we read until '/' for the first vertex index. In the second one, we read until the next space character (and ignore the result). The pointer is now at the start of the second vertex index. This process is repeated until the end of the line.

Some technical details

According to this source blank space can freely be added to the OBJ file, although this source says that "no spaces are permitted before or after the slash.". Skipping whitespace is in itself not difficult. My default approach would be to use this "state machine" to read a single line:

  1. Skip ahead until the current character is whitespace.
  2. Skip ahead until the current character is not whitespace.
  3. Read until the '/' character. This is vertex index.
  4. Back to Step 1.

Manually advancing a pointer and reading the vertex index with sscanf is a valid approach. It is tempting to try to use strtok here, however, this makes parsing more difficult as strtok

  • has a different initial call,
  • keeps the current position as internal state,
  • is destructive,
  • always looks for the next char, i.e. cannot handle a result of length 0.

Since the question indicates that the implementation should be "simple" and that usually a single space is used as a whitespace delimiter, the following implementation is possible:

Code

Example input line: f 2/1/1 4/2/1 1/3/1.

Example output line: 2 4 1 .

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define LINE_SIZE_MAX 128U

static size_t fileToLines(FILE *fp);
static bool parseFacesFromFile(FILE *fp);

int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s <obj file name>\n", argv[0]);
exit(EXIT_FAILURE);
}

FILE *fp = fopen(argv[1], "r");
if (fp == NULL)
{
fprintf(stderr, "Error opening file\n");
exit(EXIT_FAILURE);
}

printf("Number of lines: %zu\n", fileToLines(fp));

printf("Parsing %s\n", parseFacesFromFile(fp) ? "succeeded" : "failed");

fclose(fp);
}

static size_t fileToLines(FILE *fp)
{
size_t numberOfLines = 0U;
int ch;
while ((ch = getc(fp)) != EOF)
if (ch == '\n')
++numberOfLines;

fseek(fp, 0L, SEEK_SET);

return numberOfLines;
}

static bool parseFacesFromFile(FILE *fp)
{
char line[LINE_SIZE_MAX];
while (fgets(line, sizeof(line), fp) != NULL)
{
if (line[0] != 'f')
continue;

char *tokenPtr;
strtok(line, " "); // Read past the initial "f"
while ((tokenPtr = strtok(NULL, "/")) != NULL)
{
printf("%s ", tokenPtr);
strtok(NULL, " "); // Return value checking omitted, assume correct input
}
putchar('\n');
}

return true;
}

Additional comments:

  • The typical signature of main, using no parameters, is int main(void).
  • In the question's code, char ch is used uninitialized on its first use.
  • The function getc returns an int which should not be cast to a char. In the latter case, EOF cannot be reliably detected.
  • If you are only after the end result, consider using an OBJ-loading library and discarding the normals and texture data.

Trying to read the lines starting with f (indices) in a Wavefront obj file causes the other output to be broken

Solved! As it turns out I forgot to set the type for the output as an array of int and set it as vec3.



Related Topics



Leave a reply



Submit