Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding normals indices with Wavefront Obj

I've written a C++ Obj file loader that I can't get to work correctly. The problem is that while parsing a simple obj file like the following:

# Blender v2.62 (sub 0) OBJ File: ''
# www.blender.org
mtllib cube.mtl
o Cube
v 1.000000 -1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 -1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -0.999999
v 0.999999 1.000000 1.000001
v -1.000000 1.000000 1.000000
v -1.000000 1.000000 -1.000000
vn 0.000000 0.000000 -1.000000
vn -1.000000 -0.000000 -0.000000
vn -0.000000 -0.000000 1.000000
vn 1.000000 -0.000000 0.000000
vn 1.000000 0.000000 0.000001
vn -0.000000 1.000000 0.000000
vn 0.000000 -1.000000 0.000000
usemtl Material
s off
f 5//1 1//1 4//1
f 5//1 4//1 8//1
f 3//2 7//2 8//2
f 3//2 8//2 4//2
f 2//3 6//3 3//3
f 6//3 7//3 3//3
f 1//4 5//4 2//4
f 5//5 6//5 2//5
f 5//6 8//6 6//6
f 8//6 7//6 6//6
f 1//7 2//7 3//7
f 1//7 3//7 4//7

i can't understand the proper way to pass the normals to OpenGL. I always get results like this:enter image description here

ObjLoader.h

#include <Eigen/Core>

class ObjLoader
{
public:
    ObjLoader();
    bool load(const std::string &filename);

    void draw();
private:
    bool loadFace(const std::string &line,int lineNumber);


    std::vector< Eigen::Vector3d> verticesCoord,verticesNormals;
    std::vector< Eigen::Vector2d> textureCoords;
    std::vector<GLuint> vertexIndices,normalIndices,textureIndices;
    Eigen::Vector3d calculateNormal( const Eigen::Vector3d &coord1, const Eigen::Vector3d &coord2, const Eigen::Vector3d &coord3 );
    std::string mtlFile;
    unsigned int nVerticesPerFace;

};

ObjLoader.cpp

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <Eigen/Core>
#include "ObjLoader.h"

using namespace std;
using namespace Eigen;

ObjLoader::ObjLoader()
{

}

bool ObjLoader::load(const string &filename)
{
    ifstream is(filename.c_str());
    if (is.is_open())
    {
    cerr << "File " + filename + " loaded successfully" << endl;
    }

    std::vector<Vector3d> temporaryNormals; // a vector to contain the normals as they are read from the obj

    string line;
    unsigned int lineNumber=0;
    while ( getline(is,line) )
    {
    lineNumber++;
    if ( line.empty() || line.at(0)=='#' )
        continue;
    if ( line.substr(0,6)=="mtllib")
    {
        this->mtlFile = line.substr(7,line.size()-1);
        cerr <<  "MTLIB support file= " << mtlFile << endl;
    }
    stringstream stream(line);
    char identifier ;
    stream >> std::skipws >> identifier;

    char specifier;
    stream >> specifier;
    if (specifier != 't' && specifier != 'n' && specifier!='p' )
    {
        stream.seekg(0);
        specifier=0;
    }

    switch ( identifier )
    {
    case 'v': //is a vertex specification
    {
        switch ( specifier ) // if there is a space then is a simple vertex coordinates
        {
        case 0:
        {
            char tmp; stream >> tmp;
            Eigen::Vector3d vertex(0.0,0.0,0.0);
            stream >>  vertex[0] >> vertex[1] >> vertex[2];
            this->verticesCoord.push_back(vertex);
        }
            break;
        case 't':
        {
            Eigen::Vector2d textures(0,0);
            stream >> textures[0] >> textures[1];
            this->textureCoords.push_back(textures);
        }
            break;
        case 'n':
        {
            Eigen::Vector3d vertexNormal(0,0,0);
            stream >>  vertexNormal[0] >> vertexNormal[1] >> vertexNormal[2];
            temporaryNormals.push_back(vertexNormal);
        }
            break;
        }
    }
        break;
    case 'f': // is a face specification
    {
        this->loadFace(line,lineNumber);
    }
        break;
    }
    }

    // Rearrange the normals
    verticesNormals.resize(temporaryNormals.size(),Vector3d(1,1,1));
    for(unsigned int i=0;i<vertexIndices.size();i++)
    {
    GLuint nI = normalIndices.at(i);
    GLuint vI = vertexIndices.at(i);
    if(nI!=vI)
    {
        this->verticesNormals.at(vI) = temporaryNormals.at(nI);
        std::cerr<< "Normal index doesn't match vertex index: " << vertexIndices[i] << " " << normalIndices[i] << std::endl;
    }
    else
    {
        this->verticesNormals.at(vI) = temporaryNormals.at(vI);
    }

    cerr << "Vertices=" << this->verticesCoord.size() << endl;
    cerr << "Normals=" << this->verticesNormals.size() << endl;
    cerr  << "Textures=" << this->textureCoords.size() << endl;
    cerr << "NVertices per face= " << this->nVerticesPerFace << endl;
    cerr << "Faces= " << this->vertexIndices.size()/nVerticesPerFace << endl;

    return 0;
    }
}

bool BothAreSpaces(char lhs, char rhs)
{
    return (lhs == rhs) && (lhs == ' ');
}

bool ObjLoader::loadFace(const string &_line, int lineNumber)
{
    std::string line = _line;
    std::string::iterator new_end = std::unique(line.begin(), line.end(), BothAreSpaces);
    line.erase(new_end, line.end());

    stringstream stream(line),countVerticesStream(line);
    string val;
    stream >> val;
    if (val!="f")
    {
    string lineString= static_cast<ostringstream*>( &(ostringstream() << lineNumber) )->str();
    throw std::logic_error("Error loading face at line " + lineString);
    }

    // Count the number of vertices per face by counting the /
    int nVertices = 0;
    while ( countVerticesStream.good() )
    {
    if (countVerticesStream.get()==' ' && countVerticesStream.good())
        nVertices++;
    }

    if ( nVerticesPerFace !=0 && nVerticesPerFace != nVertices )
    {
    string lineString= static_cast<ostringstream*>( &(ostringstream() << lineNumber) )->str();
    throw std::logic_error("Can't support non uniform faces definitions. You must use the same number of vertices for every faces. Check line "+lineString);
    }
    this->nVerticesPerFace = nVertices;

    GLuint indexPosition = 0, indexTexture = 0, indexNormal = 0;

    // Compute the normal
    Vector3d faceVertices[nVerticesPerFace];
    for ( unsigned int iFace = 0; iFace < nVerticesPerFace; iFace++ )
    {
    stream >> indexPosition;
    faceVertices[iFace] = verticesCoord.at(indexPosition-1);
    if( '/' == stream.peek() )
    {
        stream.ignore();
        if( '/' != stream.peek() )
        {
            stream >> indexTexture;
        }
        if( '/' == stream.peek() )
        {
            stream.ignore();
            // Optional vertex normal
            stream >> indexNormal;
        }
    }
    this->vertexIndices.push_back(indexPosition-1); // that's because Obj format starts counting from 1
    this->textureIndices.push_back(indexTexture-1); // that's because Obj format starts counting from 1
    this->normalIndices.push_back(indexNormal-1); // that's because Obj format starts counting from 1

    }
}

void ObjLoader::draw()
{
    double *pVerticesCoords = &this->verticesCoord.at(0)[0];
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3,GL_DOUBLE, 0,pVerticesCoords);
    glDrawArrays(GL_POINTS, 0, this->verticesCoord.size());
    glDisableClientState(GL_VERTEX_ARRAY);

    GLint coordsPerVertex=3;
    GLint stride=0; //  Our coords are tightly packed into their arrays so we set this to 0
    //double *pVerticesCoords = &this->verticesCoord.at(0)[0];
    double *pNormalCoords = &this->verticesNormals.at(0)[0];

    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_VERTEX_ARRAY);

    glNormalPointer(GL_DOUBLE, 0, pNormalCoords); // Normal pointer to normal array
    glVertexPointer(coordsPerVertex,GL_DOUBLE, stride,pVerticesCoords);

    switch ( nVerticesPerFace )
    {
    case 3:
    glDrawElements(GL_TRIANGLES, vertexIndices.size(), GL_UNSIGNED_INT, this->vertexIndices.data());
    break;
    case 4:
    glDrawElements(GL_QUADS, vertexIndices.size(), GL_UNSIGNED_INT, this->vertexIndices.data());
    break;
    default:
    glDrawElements(GL_POLYGON, vertexIndices.size(), GL_UNSIGNED_INT, this->vertexIndices.data());
    }
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);

}

How should I reorganize the normals such that they reflect the same order of the vertices?

like image 502
linello Avatar asked Oct 22 '22 11:10

linello


1 Answers

Your problem is in the data structure. At least while loading an OBJ you need to load your faces into something like:

struct Vertex
{
    unsigned int vertex;
    unsigned int normal;
    unsigned int texturecoord;
};

struct Face
{
    // not dynamic, but you get the idea.
    Vertex vertexes[N];
};

Then you if you want an array of normals (and probably texturecoords) that matches the vertices, you need to create a new array that matches. The OBJ format is optimized for storage, not rendering.

The added benefit from this two step process, is that you can remove the restriction on homogene faces, by splinting each non triangle into a triangle.

like image 179
rioki Avatar answered Nov 04 '22 20:11

rioki