How to Read Wavefront OBJ Files Using C++/Qt

First of all let’s briefly go through what’s an OBJ File. Here’s a very brief description from Wikipedia: “OBJ (or .OBJ) is a geometry definition file format first developed by Wavefront Technologies for its Advanced Visualizer animation package. The file format is open and has been adopted by other 3D graphics application vendors.” Anyway, I’ll assume that now that since you’re here you already know what an OBJ File is and How and Where to use it.

One way (which is also my favorite way) of creating OBJ files is by using Blender. You can create your 3D shapes and objects and easily export OBJ files. Here, I used Blender to export its monkey (Suzanne) and use it later in my C++ program.





One important thing while exporting OBJ files in Blender is that you make sure that you are exporting Triangle faces. See the screenshot below:

Now let’s move to our C++ method for parsing OBJ files. First we need a structure that we will use to return. Below is what I’ve used but depending on what you want to parse you might need to add more fields:

struct QOpenGLTriangle3D
{
QVector3D p1;
QVector3D p2;
QVector3D p3;
QVector3D p1Normal;
QVector3D p2Normal;
QVector3D p3Normal;
QVector2D p1UV;
QVector2D p2UV;
QVector2D p3UV;
};





And below is the OBJ parser method itself:

void parseObjFile(const QString &fileName,
QStringList &comments,
QVector<QOpenGLTriangle3D> &triangles)
{
comments.clear();
triangles.clear();

QFile file(fileName);
if(file.exists())
{
if(file.open(QFile::ReadOnly | QFile::Text))
{
QVector<QVector3D> v, vn;
QVector<QVector2D> vt;

while(!file.atEnd())
{
QString line = file.readLine().trimmed();
QStringList lineParts = line.split(QRegularExpression(“\\s+”));
if(lineParts.count() > 0)
{

// if it’s a comment
if(lineParts.at(0).compare(“#”, Qt::CaseInsensitive) == 0)
{
comments.append(line.remove(0, 1).trimmed());
}

// if it’s a vertex position (v)
else if(lineParts.at(0).compare(“v”, Qt::CaseInsensitive) == 0)
{
v.append(QVector3D(lineParts.at(1).toFloat(),
lineParts.at(2).toFloat(),
lineParts.at(3).toFloat()));
}

// if it’s a normal (vn)
else if(lineParts.at(0).compare(“vn”, Qt::CaseInsensitive) == 0)
{
vn.append(QVector3D(lineParts.at(1).toFloat(),
lineParts.at(2).toFloat(),
lineParts.at(3).toFloat()));
}

// if it’s a texture (vt)
else if(lineParts.at(0).compare(“vt”, Qt::CaseInsensitive) == 0)
{
vt.append(QVector2D(lineParts.at(1).toFloat(),
lineParts.at(2).toFloat()));
}

// if it’s face data (f)
// there’s an assumption here that faces are all triangles
else if(lineParts.at(0).compare(“f”, Qt::CaseInsensitive) == 0)
{
QOpenGLTriangle3D triangle;

// get points from v array
triangle.p1 = v.at(lineParts.at(1).split(“/”).at(0).toInt() – 1);
triangle.p2 = v.at(lineParts.at(2).split(“/”).at(0).toInt() – 1);
triangle.p3 = v.at(lineParts.at(3).split(“/”).at(0).toInt() – 1);

if(vt.count() > 0) // check if really there are any UV coords
{
triangle.p1UV = vt.at(lineParts.at(1).split(“/”).at(1).toInt() – 1);
triangle.p2UV = vt.at(lineParts.at(2).split(“/”).at(1).toInt() – 1);
triangle.p3UV = vt.at(lineParts.at(3).split(“/”).at(1).toInt() – 1);
}

// get normals from vn array
triangle.p1Normal = vn.at(lineParts.at(1).split(“/”).at(2).toInt() – 1);
triangle.p2Normal = vn.at(lineParts.at(2).split(“/”).at(2).toInt() – 1);
triangle.p3Normal = vn.at(lineParts.at(3).split(“/”).at(2).toInt() – 1);

triangles.append(triangle);
}

}
}

file.close();
}
}
}





Here’s the same monkey parsed from OBJ file and rendered in a Qt app:





Leave a Reply

Your email address will not be published. Required fields are marked *