How can I parse a simple .obj file into triangles?

巧了我就是萌 提交于 2021-01-29 05:32:07

问题


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.


回答1:


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.



来源:https://stackoverflow.com/questions/52824956/how-can-i-parse-a-simple-obj-file-into-triangles

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!