-6

I am writing a raytracer program in C++. Part of this program should be able to read a 3D model from a mesh.obj file.

I am currently having problems with reading the file using std::ifstream.

I wrote a function read_mesh, which takes a string parameter that specifies the path to the .obj file, given by a user. The function returns a list of triangle Primitives which are used to render the mesh.

I am using LOG(INFO) << "specific info"; so that I'm able to view what is going on in my program.

This is my function:

std::vector<Primitive> read_mesh(const std::string& path) const
    {
        LOG(INFO) << "Reading mesh";

        LOG(INFO) << "Attempting to open file at " << path;

        std::ifstream obj;

        obj.exceptions(std::ifstream::failbit | std::ifstream::badbit);

        // Open the file from the given path
        try {
            obj.open(path, ios::in);
        }
        catch (std::ifstream::failure::exception e) {
            LOG(INFO) << "Exception when opening file \n" << e.what();
        }

        LOG(INFO) << "File opened";

        // Initialize list of vertices
        std::vector<Point3D> vertices;

        // Initialize list of triangles
        std::vector<Primitive> triangles;

        std::string line;

        // Read each line of the .obj file
        while (std::getline(obj, line))
        {
            LOG(INFO) << "New line";

            std::istringstream iss(line);
            std::string method;

            // Extract the type of data from the line
            iss >> method;

            // Checks if the data on the line represents a vertex
            if (method == "v")
            {
                double x, y, z;

                // Extract the x, y and z coordinates from the line
                iss >> x >> y >> z;

                LOG(INFO) << "New vertex (" << x << ", " << y << ", " << z << ")";

                // Create a Point3D from the coordinates
                Point3D p(x, y, z);

                // Add the point to the list of vertices
                vertices.push_back(p);
            }

            // Checks if the data on the line represents a set of triangles
            else if (method == "f")
            {
                int a, b, c;
                char s;

                // Read the set of triangles from the polygon line with the index of the vertices seperated by a '/'
                while ((iss >> a >> s >> b >> s >> c) && s == '/')
                {
                    LOG(INFO) << "New triangle (v" << a << ", v" << b << ", v" << c << ")";

                    // Create a triangle from the corresponding vertices (index -1 because they start at 1 in .obj files) and add it to the triangles list
                    triangles.push_back(triangle(vertices[a - 1], vertices[b - 1], vertices[b - 1]));
                }
            }
            else
            {
                LOG(INFO) << "Ignored this line";
            }
        }
        if (!std::getline(obj, line))
        {
            LOG(INFO) << "Reached end of file";
        }

        obj.close();

        LOG(INFO) << "Finished reading mesh, " << triangles.size() << " triangles found";

        // Return the list of triangles
        return triangles;
    }

I can see each line being read correctly, and after the last line, no more lines are being read. But instead of printing "Reached end of file" and "Finished reading mesh, 290 triangles found", the program stops executing.

So I believe the problem is that the program never actually exits the while loop, although it stops printing "New line" and "Reached end of line".

Can anyone help me with finding the problem in this function? Any help is greatly appreciated, thanks!

EDIT:

Here is a sample application that uses the same functionality as the above function:

int main()
{

std::ifstream obj;

obj.exceptions(std::ifstream::failbit | std::ifstream::badbit);

try {
    // Enter the path to the example.obj file
    obj.open("C:/Users/Bram/Desktop/example-obj.txt", ios::in);
}
catch (std::ifstream::failure::exception e) {
    std::cout << "Failed to open file";
    return(1);
}

std::cout << "Reading file";

std::string line;

while (std::getline(obj, line))
{
    std::cout << "New line";

    std::istringstream iss(line);
    std::string method;

    iss >> method;

    if (method == "v")
    {
        double x, y, z;

        iss >> x >> y >> z;

        std::cout << "New vertex (" << x << ", " << y << ", " << z << ")";
    }

    else if (method == "f")
    {
        int a, b, c;
        char s;

        while ((iss >> a >> s >> b >> s >> c) && s == '/')
        {
            std::cout << "New triangle using vertices " << a << ", " << b << ", " << c;
        }
    }
    else
    {
        std::cout << "Ignored line";
    }
}

std::cout << "Finished reading file";

obj.close();

return(0);
}

It reads the following example-obj.txt file that represents one triangle:

# example.obj

v  -7.8541 121.3862 17.1093
v  7.1431 113.8654 23.1824
v  15.3955 112.2824 13.7628

f  1/2/3

The program prints the correct vertices and triangles and gives the following error message when executing:

Unhandled exception at 0x75EA08B2 in meshreader.exe: Microsoft C++ exception: std::ios_base::failure at memory location 0x008FF3F0.
  • 7
    Sounds like an opportunity to learn how to use the debugger. Otherwise create a super minimal .obj file that reproduces the behavior and post a [mcve] that we can compile ourselves. – AndyG Jan 17 '18 at 12:50
  • 1
    if (!std::getline(obj, line)) { LOG(INFO) << "Reached end of file"; } is that not reading past the end of line. I think the if statement is not needed. – falopsy Jan 17 '18 at 12:53
  • 1
    @falopsy: You're right that it is not needed because the previous loop appears to only terminate when getline returns false. However, it will not read past the end of the line/file; only return false after checking the stream's status bits. – AndyG Jan 17 '18 at 12:55
  • Where is the code catching and handling the exception that is being thrown by the ifstream? – Eljay Jan 17 '18 at 13:28
  • @AndyG • the getline won't return false, since the ifstream is set to throw an exception. The while loop is being terminated via exception. – Eljay Jan 17 '18 at 13:36
  • @Eljay: You're right, I didn't see the `obj.exceptions`. I don't think OP has provided us with a real MCVE – AndyG Jan 17 '18 at 13:44
  • @AndyG Sorry, it's my first time asking a question here, thanks for pointing that out. Please view the edited part of my post, I now get an error message specifying an unhandled exception. – Bram Van Asschodt Jan 17 '18 at 14:23

1 Answers1

0
obj.exceptions(std::ifstream::failbit | std::ifstream::badbit);

This sets the internal state of the stream object so that it will throw an exception if an I/O operation tries to set either of those bits.

while (std::getline(obj, line))

This says to keep looping until the call to std::getline fails. Ordinarily that works because the call to std::getline will fail when std::getline is called after the last line has been read.

But in this case, the stream has been set to throw an exception when the attempted input fails. So the loop doesn't terminate normally, because the final call to std::getline throws an exception.

If you remove the line that sets the stream to throw exceptions things will work the way you expect them to.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • thanks for pointing out and explaining that mistake! I deleted the line and the sample works now, however after making the changes to my actual function, the program gets stuck on the line `obj.open(path, ios::in)`. Could there be something wrong with the path, or am I catching the wrong exception here? – Bram Van Asschodt Jan 17 '18 at 15:25
  • Some memory/files exceptions are not automatically thrown except if compiled with the /Eha option (In visual studio, Properties>>Code Generation>>Enable C++ Exception>>Yes with SEH Exceptions). – falopsy Jan 18 '18 at 11:50
  • You can also put catch std::exception and catch(...) after your ifstream exception in case another exception type is thrown. – falopsy Jan 18 '18 at 11:51
  • Better coding style is to catch your excepting as const reference i.e. catch(const std::exception& e){}; – falopsy Jan 18 '18 at 11:51