For anyone interested here is how I parsed my obj file with stringstream style instead of sscanf style
Old code:
for(;;)
{
if(fgets(line_buffer, sizeof(line_buffer), in) == NULL)
{
error= false; // eof
break;
}
// force endl
line_buffer[sizeof(line_buffer) -1]= 0;
// skip spaces
char *line= line_buffer;
while(*line && isspace(*line))
line++;
if(line[0] == 'v')
{
float x, y, z;
if(line[1] == ' ') // position x y z
{
if(sscanf(line, "v %f %f %f", &x, &y, &z) != 3)
break;
positions.push_back( vec3(x, y, z) );
}
else if(line[1] == 'n') // normal x y z
{
if(sscanf(line, "vn %f %f %f", &x, &y, &z) != 3)
break;
normals.push_back( vec3(x, y, z) );
}
else if(line[1] == 't') // texcoord x y
{
if(sscanf(line, "vt %f %f", &x, &y) != 2)
break;
texcoords.push_back( vec2(x, y) );
}
}
else if(line[0] == 'f')
{
idp.clear();
idt.clear();
idn.clear();
int next;
for(line= line +1; ; line= line + next)
{
idp.push_back(0);
idt.push_back(0);
idn.push_back(0); // 0: invalid index
next= 0;
if(sscanf(line, " %d/%d/%d %n", &idp.back(), &idt.back(), &idn.back(), &next) == 3)
continue;
else if(sscanf(line, " %d/%d %n", &idp.back(), &idt.back(), &next) == 2)
continue;
else if(sscanf(line, " %d//%d %n", &idp.back(), &idn.back(), &next) == 2)
continue;
else if(sscanf(line, " %d %n", &idp.back(), &next) == 1)
continue;
else if(next == 0) //endl
break;
}
}
New code: (with the @JerryCoffin code for std::istream& operator>>(std::istream& is, char const* s);)
std::string line;
while (getline(file, line))
{
std::stringstream ss(line);
std::string tag;
// istream& operator>> skips whitespaces unless std::skipws is disable
// ignore empty lines
if (!(ss >> tag))
continue;
// ignore comments
if (tag[0] == '#')
continue;
float x, y, z;
if (tag == "v")
{
if (!(ss >> x >> y >> z))
break;
positions_tmp.emplace_back(x, y, z);
}
else if (tag == "vt")
{
if (!(ss >> x >> y))
break;
texcoords_tmp.emplace_back(x, y);
}
else if (tag == "vn")
{
if (!(ss >> x >> y >> z))
break;
normals_tmp.emplace_back(x, y, z);
}
else if (tag == "f")
{
for (int i = 0; i < 3; ++i)
{
unsigned int idp(0), idt(0), idn(0);
// reset stringstream after each read so that it keeps current position when fail
auto pos = ss.tellg();
auto state = ss.rdstate() && ~std::ios_base::failbit;
auto reset = [state, pos](std::stringstream& ss)
{ ss.clear(state); ss.seekg(pos); return !ss.fail(); };
// will try to match either i//i or i/i/i or i/i or i (all .obj "f" configurations)
if (reset(ss) && ss >> idp >> "//" >> idn) {}
else if (reset(ss) && ss >> idp >> "/" >> idt >> "/" >> idn) {}
else if (reset(ss) && ss >> idp >> "/" >> idt) {}
else if (reset(ss) && ss >> idp) {}
// do stuff with indexes
// ...
}
}
}
I changed a bit of logic here and there but the trickiest part was on the f i/i/i i/i/i i/i/i lines where you have to check if it matches right. If it doesn't you have to reset the stringstream to it's original state and position (at least remove std::ios_base::failbit and and replace position).
I wanted to keep the aspect of one line = one case, so it might be a little harder to understand with the lambda function, but I could also reverse the logic and handle faces edge by edge (not sure if I'll use it though).