0

我写了一个基本的 .obj 加载器。它适用于立方体和其他一些基本的东西。一旦我给它一个复杂的模型,它就会失败。

这是一些基本代码:

函数.h

#pragma once
#include <string>

struct face {
    bool triangle;
    int faceNumber;
    int faces[4];
    face(int faceNumber2,int f1,int f2, int f3, int f4) {
        triangle = false;
        faceNumber = faceNumber2;
        faces[0] = f1;
        faces[1] = f2;
        faces[2] = f3;
        faces[3] = f4;
    }
    face(int faceNumber2,int f1,int f2, int f3) {
        triangle = true;
        faceNumber = faceNumber2;
        faces[0] = f1;
        faces[1] = f2;
        faces[2] = f3;
    }


};

struct position {
    float x, y, z;
    position(float X,float Y, float Z) {
        x = X;
        y = Y;
        z = Z;
    }
};

class Functions
{
public:
    Functions(void);
    ~Functions(void);
    int loadObject(std::string fileName);
    void drawCube(float size);
    unsigned int loadTexture(const char* fileName);
};

函数.cpp

int Functions::loadObject(string fileName) {
    ifstream file(fileName);
    vector<string*> line;
    vector<position*> normals;
    vector<face*> faces;
    vector<position*> vertices;



    if (file.is_open()) {
        char buffer[256];
        while (!file.eof()) {
            file.getline(buffer,256);
            line.push_back(new string(buffer));
        }
        for (int i = 0; i<line.size(); i++) {
            if ((*line[i])[0] == 'v' && (*line[i])[1] == ' ') { //Vertice
                float x,y,z;
                sscanf_s((*line[i]).c_str(),"v %f %f %f",&x,&y,&z);
                vertices.push_back(new position(x,y,z));
            }else if ((*line[i])[0] == 'v' && (*line[i])[1] == 'n') { //Normals
                float x,y,z;
                sscanf_s((*line[i]).c_str(),"vn %f %f %f",&x,&y,&z);
                normals.push_back(new position(x,y,z));
            } else if ((*line[i])[0] == 'f') {
                if (count((*line[i]).begin(),(*line[i]).end(),' ') == 4) {
                    float a,b,c,d,e;
                    sscanf_s((*line[i]).c_str(),"f %f//%f %f//%f %f//%f %f//%f",&a,&e,&b,&e,&c,&e,&d,&e);

                    faces.push_back(new face(e,a,b,c,d));
                } else {
                    float a,b,c,e;
                    sscanf_s((*line[i]).c_str(),"f %f//%f %f//%f %f//%f",&a,&e,&b,&e,&c,&e);
                    faces.push_back(new face(e,a,b,c));
                }

            }
        }
        file.close();
    } else {
        file.close();
        throw exception("Fail to load file");
        return -1;
    }

    int num;
    num = glGenLists(1);
    glNewList(num,GL_COMPILE);
        for (int i = 0; i<faces.size(); i++) {
            face tempFace = (*faces[i]);
            if (tempFace.triangle) {
                glBegin(GL_TRIANGLES);
                    glNormal3f((*normals[tempFace.faceNumber-1]).x, (*normals[tempFace.faceNumber-1]).y, (*normals[tempFace.faceNumber-1]).z);
                    glVertex3f((*vertices[tempFace.faces[0]-1]).x, (*vertices[tempFace.faces[0]-1]).y, (*vertices[tempFace.faces[0]-1]).z);
**//Errors below.**
                    glVertex3f((*vertices[tempFace.faces[1]-1]).x, (*vertices[tempFace.faces[1]-1]).y, (*vertices[tempFace.faces[1]-1]).z);
                    glVertex3f((*vertices[tempFace.faces[2]-1]).x, (*vertices[tempFace.faces[2]-1]).y, (*vertices[tempFace.faces[2]-1]).z);
                glEnd();
            } else {
            //glNormal3f((*normals[tempFace.faceNumber-1]).x,(*normals[tempFace.faceNumber-1]).y,(*normals[tempFace.faceNumber-1]).z)
                glBegin(GL_QUADS);
                    glNormal3f((*normals[tempFace.faceNumber-1]).x, (*normals[tempFace.faceNumber-1]).y, (*normals[tempFace.faceNumber-1]).z);
                    glVertex3f((*vertices[tempFace.faces[0]-1]).x, (*vertices[tempFace.faces[0]-1]).y, (*vertices[tempFace.faces[0]-1]).z);
                    glVertex3f((*vertices[tempFace.faces[1]-1]).x, (*vertices[tempFace.faces[1]-1]).y, (*vertices[tempFace.faces[1]-1]).z);
                    glVertex3f((*vertices[tempFace.faces[2]-1]).x, (*vertices[tempFace.faces[2]-1]).y, (*vertices[tempFace.faces[2]-1]).z);
                    glVertex3f((*vertices[tempFace.faces[3]-1]).x, (*vertices[tempFace.faces[3]-1]).y, (*vertices[tempFace.faces[3]-1]).z);
                glEnd();
            }
        }
    glEndList();


    for (int i = 0; i<line.size(); i++) {
        delete line[i];
    }
    for (int i = 0; i<normals.size(); i++) {
        delete normals[i];
    }
    for (int i = 0; i<vertices.size(); i++) {
        delete vertices[i];
    }
    for (int i = 0; i<faces.size(); i++) {
        delete faces[i];
    }

    return 1;
}

我已经尝试了两天了。就像我说的那样,它适用于一些不复杂的模型。

4

1 回答 1

3

代码有几个问题,每个问题在技术上都需要一个单独的答案。但是,好吧,就这样吧。如果你想使用 C++,那就做对吧。首先,我们希望我们的对象具有 RAII 能力。这意味着我们需要一个复制构造函数和一个赋值运算符。

// A face should always be a triangle. Quads are ambigous
// when it comes to rendering, and most OpenGL implementations
// break them into triangles anyway. We can do this better.
// Also the "faceNumber" element doesn't do what you think it
// does. We get back to that later.
struct face {
    int i[3];

    void copy(face const &f_) {
        for(int j = 0; j < 3; j++) { // could use memcpy as well
            i[j] = f_.i[j]
        }
    }
    face const &operator=(face const &f_) {
        copy(f_);
        return *this;
    }
    face(face const &f_) { copy(f_); }
    face(int i_a, int i_b, int i_c) {
        i[0] = i_a;
        i[1] = i_b;
        i[2] = i_c;

    }
};

struct tuple3f {
    float x, y, z;

    tuple3f(float x_,float y_, float z_) : x(x_), y(y_), z(z_) {}
    tuple3f(tuple3f const &p_) : x(p_.x), y(p_.y), z(p_.z) {}
    tuple3f const &operator=(tuple3f const &p_) {
        x = p_.x; y = p_.y; z = p_.z;
        return *this;
    }
};

// Struct for unrolling the vertices into.
struct vertex3p3n {
     tuple3f position, normal;

     // Implementing the constructor, copy constructor and
     // assignment operator is left as en exercise.   
     vertex3p3n(tuple3 const &p_, tuple3 const &n_) : ... {}
};

namespace glhelpers {
    enum error {
        NoError = 0,
        FileNotOpened
    };

    error loadWavefrontOBJ(
            std::string fileName,
            std::vector<vertex3p3n> &out_unrolledvertexarray ) {
        ifstream file(fileName);

        vector<tuple3f> positions;
        vector<tuple3f> normals;

        vector<face>    faces_position;
        vector<face>    faces_normal;

        if (file.is_open()) {
            // It's totally inefficient to first load a file line by line
            // into memory, making a new allocation for each line's string
            // which internally does another new allocation,
            // only to process it line by line later.
            //
            // Just do the line by line processing on the read lines without
            // intermediary allocation. And there's a nice std::getline
            // function which does the job just nicely for us.
            while( !file.eof() ) {
                std::string line;
                std::getline(file, line);

                if( 'v' == line[0] && ' ' == line[1] ) { // Vertex
                    float x,y,z;
                    sscanf_s(line.c_str()+2, "%f %f %f", &x,&y,&z);
                    positions.push_back(tuple3f(x,y,z));
                    continue;
                }

                if( 'v' == line[0] && 'n' == line[1] ) { // Normal
                    float x,y,z;
                    sscanf_s(line[i].c_str()+3, "%f %f %f", &x,&y,&z);
                    normals.push_back(tuple3f(x,y,z));
                    continue;
                }

                // Here begins the nasty part. The Wavefront OBJ file format
                // treats positions, normals and texture coordinates as separate
                // entities. Which is fine, as long as you don't want to shove
                // them into a renderer like OpenGL is. You see, in OpenGL
                // every *vertex* consists of the whole combination of
                // [position, normal, texture coordinate, ...] and other attributes.
                // They're not separable (even if the immediate mode API may make
                // people think set). Which leads to a problem: Somehow we've to
                // unite those later on. There are several ways to do this.
                // Renumbering and unrolling are the easiest once. Putting the
                // numbers into immediate mode is a form of unrolling.
                //
                // What the Wavefront actually is giving you for each face is the
                // index into the separated position, texture coordinate and
                // normal arrays.
                if ( 'f' == line[0] ) {
                    if( count(line.begin(), line.end(), ' ') == 4) {
                        float pa, pb, pc, pd;
                        float na, nb, nc, nd;
                        sscanf_s(
                             line.c_str()+2
                             "%f//%f %f//%f %f//%f %f//%f",
                             &pa,&na, &pb,&nb, &pc,&nc, &pd,&nd);

                        faces_position.push_back(face(pa, pb, pc));
                        faces_normal.push_back(  face(na, nb, nc));

                        // Decompose the quad into two triangles

                        faces_position.push_back(face(pc, pd, pa));
                        faces_normal.push_back(  face(nc, nd, na));
                    } else {
                        float pa, pb, pc;
                        float na, nb, nc;
                        sscanf_s(
                            line.c_str()+2,"%f//%f %f//%f %f//%f",
                            &pa,&na, &pb,&nb, &pc,&nc);

                        faces_position.push_back(face(pa, pb, pc));
                        faces_normal.push_back(  face(na, nb, nc));
                    }
                    continue;
                }
            }
            file.close();
        } else {
            // If the file couldn't be opened, there's no point
            // in trying to close it.

            // throwing an exception diverts the regular control
            // flow, i.e. the function won't return.
            // My advice: Don't use exceptions, they're harmful
            // in most situations. Use proper error codes instead.
            return FileNotOpened;
        }

        out_unrolledvertexarray.resize(faces_position.size()*3),
        for(int j = 0; j < faces_position.size(); j++) {
            for(int k = 0; k < 3; k++ {
                out_unrolledvertexarray[j*3 + k] =
                    vertex3p3n(
                        positions[faces_positions[j].i[k]],
                        normals[  faces_normals  [j].i[k]]);
            }
        }
        return NoError;
    }
}

现在我们怎么画这个?好吧,我们为此使用顶点数组。在陈旧而尘封的固定功能管道中,它们通过客户端状态和 gl…指针功能进行访问。

std::vector<vertex3p3n> model;

void loadmodel();
{  // Do this only one time at startup
    glhelpers::loadWavefrontOBJ("...", model);
}

void drawmodel()
{
    glEnableClientState(GL_VERTEX_ARRAY); // a much better name was GL_POSITION_ARRAY
    glEnableClientState(GL_NORMAL_ARRAY);

    glVertexPointer(
        3, // 3 elements per position tuple
        GL_FLOAT, // each element is a float
        sizeof(vertex3p3n), // distance between vertex position[0]
        &model[0].position );

    glNormalPointer( // a normal always has 3 elements
        GL_FLOAT, // each element is a float
        sizeof(vertex3p3n), // distance between vertex position[0]
        &model[0].normal );

    glDrawArrays(GL_TRIANGLES, 0, model.size();
 }
于 2013-09-09T01:15:16.927 回答