1

I'm trying to implement a ray caster and I'm starting out with simple .obj files (utah-teapot) and currently I only made classes for Spheres and Triangles, I basically have all the functions for the intersections, generating view rays, etc.. all ready but I just can't seem to be able to parse the .obj file into triangles (three vectors each) so I can have the ray casting possible on custom .obj files instead of just spheres.

This is my current .obj file parser (didn't include the full working code here)

char lineHeader[512];
// read the first word of the line
int res = fscanf(file, "%s", lineHeader);
if (res == EOF)
    break; // EOF

// else : parse lineHeader

if (strcmp(lineHeader, "v") == 0) {
    glm::vec3 vertex;
    fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z);
    vertex.x *= scale;
    vertex.y *= scale;
    vertex.z *= scale;
    temp_vertices.push_back(vertex);
}
else if (strcmp(lineHeader, "vt") == 0) {
    glm::vec2 uv;
    fscanf(file, "%f %f\n", &uv.x, &uv.y);
    uv.y = -uv.y; // Invert V coordinate since we will only use DDS texture, which are inverted. Remove if you want to use TGA or BMP loaders.
    temp_uvs.push_back(uv);
}
else if (strcmp(lineHeader, "vn") == 0) {
    glm::vec3 normal;
    fscanf(file, "%f %f %f\n", &normal.x, &normal.y, &normal.z);
    temp_normals.push_back(normal);
}
else if (strcmp(lineHeader, "f") == 0) {
    std::string vertex1, vertex2, vertex3;
    unsigned int vertexIndex[3] = { 0 }, uvIndex[3] = { 0 }, normalIndex[3] = { 0 };
    char stupidBuffer[1024];
    fgets(stupidBuffer, 1024, file);
    int matches = sscanf(stupidBuffer, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2]);
    if (matches != 9) {
        vertexIndex[3] = { 0 }, uvIndex[3] = { 0 }, normalIndex[3] = { 0 };
        matches = sscanf(stupidBuffer, "%d//%d %d//%d %d//%d\n", &vertexIndex[0], &normalIndex[0], &vertexIndex[1], &normalIndex[1], &vertexIndex[2], &normalIndex[2]);
        if (matches != 6) {
            vertexIndex[3] = { 0 }, uvIndex[3] = { 0 }, normalIndex[3] = { 0 };
            matches = sscanf(stupidBuffer, "%d %d %d\n", &vertexIndex[0], &vertexIndex[1], &vertexIndex[2]);
            if (matches != 3) {
                printf("File can't be read \n");
                fclose(file);
                return false;
            }
        }
    }
}

This is my triangle class

class Triangle {
public:
    Vector p0, p1, p2;
    Vector color;
    Vector normal(void);
};

I can't figure out how to parse the info from the .obj file into triangles that consist of three 3d vectors (points). I don't need code, I just need to understand how (if possible?) to parse all that info into triangles. Any other ideas are welcome. I want to make a simple puzzle game on the long run but I'm just taking it a step at a time.

genpfault
  • 51,148
  • 11
  • 85
  • 139
Josh Aston
  • 21
  • 1
  • 5
  • @genpfault I just edited in my parser, hope that helps, I'm not looking for code, I just need a way to better understand how to parse the .obj file to my Triangle class, if possible – Josh Aston Oct 16 '18 at 17:06
  • Much better, thank you! I voted to re-open. – genpfault Oct 16 '18 at 17:28
  • 1
    @JoshAston see [How do I sort the texture positions based on the texture indices given in a Wavefront (.obj) file?](https://stackoverflow.com/a/51722009/2521214) My parser there do exactly what you are looking for ... – Spektre Oct 17 '18 at 07:43
  • This is one of the rare cases where a working code could actually be a pretty good answer, if you can read and understand it. Anyway, I'd really consider using something else to parse rather than the C library classes. – Bartek Banachewicz Oct 17 '18 at 10:04
  • haha No you cant do that with `\n` . https://github.com/alexeyneu/glsl-base/blob/bb7eae8f12045c35b3eb93b5eeacd0fcba7eaaff/t.c#L116 – Алексей Неудачин Oct 17 '18 at 23:40

1 Answers1

3

You're 90% of the way there. In your face element parser use the parsed position/normal/texcoord indexes of each face-vertex to grab info from the temp_* vectors. If a face element has three vertices you can emit a triangle as-is, otherwise for 4+ vertices I've generally assumed the the resulting polygon is convex & co-planar, in which case you can triangulate by pretending it's a triangle fan.

All together:

struct Vertex
{
    glm::vec3 position;
    glm::vec2 texcoord;
    glm::vec3 normal;
};

struct VertRef
{
    VertRef( int v, int vt, int vn ) : v(v), vt(vt), vn(vn) { }
    int v, vt, vn;
};

std::vector< Vertex > LoadOBJ( std::istream& in )
{
    std::vector< Vertex > verts;

    std::vector< glm::vec4 > positions( 1, glm::vec4( 0, 0, 0, 0 ) );
    std::vector< glm::vec3 > texcoords( 1, glm::vec3( 0, 0, 0 ) );
    std::vector< glm::vec3 > normals( 1, glm::vec3( 0, 0, 0 ) );
    std::string lineStr;
    while( std::getline( in, lineStr ) )
    {
        std::istringstream lineSS( lineStr );
        std::string lineType;
        lineSS >> lineType;

        // vertex
        if( lineType == "v" )
        {
            float x = 0, y = 0, z = 0, w = 1;
            lineSS >> x >> y >> z >> w;
            positions.push_back( glm::vec4( x, y, z, w ) );
        }

        // texture
        if( lineType == "vt" )
        {
            float u = 0, v = 0, w = 0;
            lineSS >> u >> v >> w;
            texcoords.push_back( glm::vec3( u, v, w ) );
        }

        // normal
        if( lineType == "vn" )
        {
            float i = 0, j = 0, k = 0;
            lineSS >> i >> j >> k;
            normals.push_back( glm::normalize( glm::vec3( i, j, k ) ) );
        }

        // polygon
        if( lineType == "f" )
        {
            std::vector< VertRef > refs;
            std::string refStr;
            while( lineSS >> refStr )
            {
                std::istringstream ref( refStr );
                std::string vStr, vtStr, vnStr;
                std::getline( ref, vStr, '/' );
                std::getline( ref, vtStr, '/' );
                std::getline( ref, vnStr, '/' );
                int v = atoi( vStr.c_str() );
                int vt = atoi( vtStr.c_str() );
                int vn = atoi( vnStr.c_str() );
                v  = (  v >= 0 ?  v : positions.size() +  v );
                vt = ( vt >= 0 ? vt : texcoords.size() + vt );
                vn = ( vn >= 0 ? vn : normals.size()   + vn );
                refs.push_back( VertRef( v, vt, vn ) );
            }

            // triangulate, assuming n>3-gons are convex and coplanar
            for( size_t i = 1; i+1 < refs.size(); ++i )
            {
                const VertRef* p[3] = { &refs[0], &refs[i], &refs[i+1] };

                // http://www.opengl.org/wiki/Calculating_a_Surface_Normal
                glm::vec3 U( positions[ p[1]->v ] - positions[ p[0]->v ] );
                glm::vec3 V( positions[ p[2]->v ] - positions[ p[0]->v ] );
                glm::vec3 faceNormal = glm::normalize( glm::cross( U, V ) );

                for( size_t j = 0; j < 3; ++j )
                {
                    Vertex vert;
                    vert.position = glm::vec3( positions[ p[j]->v ] );
                    vert.texcoord = glm::vec2( texcoords[ p[j]->vt ] );
                    vert.normal = ( p[j]->vn != 0 ? normals[ p[j]->vn ] : faceNormal );
                    verts.push_back( vert );
                }
            }
        }
    }

    return verts;
}

See the full program here.

genpfault
  • 51,148
  • 11
  • 85
  • 139