OBJ Loader won't work

I’m currently trying to implement my own OBJ Loader and I finally feel like I’ve got close to getting it working. This is my OBJ Loader:


#include "OBJLoader.h"
#include <fstream>
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <map>

Mesh* OBJLoader::LoadModel(char* path)
{
    Mesh* mesh = new Mesh(); //New Mesh

    std::vector<Vector3> vertices;
    std::vector<TexCoord> textureCoords;
    std::vector<Vector3> normals;

    std::vector<Vertex> vertexes;
    std::map<GLushort, Vertex> indices;

    //Loads OBJ file from path
    std::ifstream file;
    file.open(path);
    //std::cout << path << std::endl;
    if (!file.good())
    {
        std::cout << "Can't open texture file " << path << std::endl;
        return nullptr;
    }

    std::string text;
    std::string line;
    while (!file.eof())
    {
        file >> text;
        if (text == "v")
        {
            Vector3 vertice;
            file >> vertice.X;
            file >> vertice.Y;
            file >> vertice.Z;

            vertices.push_back(vertice);
        }

        if (text == "vt")
        {
            mesh->hasTexCoords = true;

            TexCoord texCoord;
            file >> texCoord.U;
            file >> texCoord.V;

            textureCoords.push_back(texCoord);
        }

        if(text == "vn")
        {
            mesh->hasNormals = true;

            Vector3 normal;
            file >> normal.X;
            file >> normal.Y;
            file >> normal.Z;

            normals.push_back(normal);
        }

        if (text == "f")
        {
            std::string vertex1;
            std::string vertex2;
            std::string vertex3;

            std::string vertexString;
            std::string texCoordString;
            std::string normalString;

            Vertex mainVertex;

            if (mesh->hasTexCoords && !mesh->hasNormals)
            {
                file >> vertex1;
                file >> vertex2;

                std::stringstream ss(vertex1);
                std::getline(ss, vertexString, '/');
                std::getline(ss, texCoordString, '/');

                mainVertex.vertice = std::stoi(vertexString);
                mainVertex.texCoord = std::stoi(texCoordString);

                vertexes.push_back(mainVertex);

                ss.str("");
                ss.clear();
                ss << vertex2;
                std::getline(ss, vertexString, '/');
                std::getline(ss, texCoordString);

                mainVertex.vertice = std::stoi(vertexString);
                mainVertex.texCoord = std::stoi(texCoordString);

                vertexes.push_back(mainVertex);
            }
            else if(mesh->hasTexCoords && mesh->hasNormals)
            {
                file >> vertex1;
                file >> vertex2;
                file >> vertex3;

                std::stringstream ss(vertex1);
                std::getline(ss, vertexString, '/');
                std::getline(ss, texCoordString, '/');
                std::getline(ss, normalString);

                mainVertex.vertice = std::stoi(vertexString);
                mainVertex.texCoord = std::stoi(texCoordString);
                mainVertex.normal = std::stoi(normalString);

                vertexes.push_back(mainVertex);

                ss.str("");
                ss.clear();
                ss << vertex2;
                std::getline(ss, vertexString, '/');
                std::getline(ss, texCoordString, '/');
                std::getline(ss, normalString);

                mainVertex.vertice = std::stoi(vertexString);
                mainVertex.texCoord = std::stoi(texCoordString);
                mainVertex.normal = std::stoi(normalString);

                vertexes.push_back(mainVertex);

                ss.str("");
                ss.clear();
                ss << vertex3;
                std::getline(ss, vertexString, '/');
                std::getline(ss, texCoordString, '/');
                std::getline(ss, normalString);

                mainVertex.vertice = std::stoi(vertexString);
                mainVertex.texCoord = std::stoi(texCoordString);
                mainVertex.normal = std::stoi(normalString);

                vertexes.push_back(mainVertex);
            }
        }
    }

    file.close();

    mesh->vertexCount = vertices.size();
    mesh->vertices = new Vector3[mesh->vertexCount];
    for (int i = 0; i < mesh->vertexCount; i++)
    {
        mesh->vertices[i].X = vertices[i].X;
        mesh->vertices[i].Y = vertices[i].Y;
        mesh->vertices[i].Z = vertices[i].Z;
    }

    if (mesh->hasTexCoords)
    {
        mesh->texCoordCount = textureCoords.size();
        mesh->texCoords = new TexCoord[mesh->texCoordCount];
        for (int i = 0; i < mesh->texCoordCount; i++)
        {
            mesh->texCoords[i].U = textureCoords[i].U;
            mesh->texCoords[i].V = textureCoords[i].V;
        }
    }

    if (mesh->hasNormals)
    {
        mesh->normalCount = normals.size();
        mesh->normals = new Vector3[mesh->normalCount];
        for (int i = 0; i < mesh->normalCount; i++)
        {
            mesh->normals[i].X = normals[i].X;
            mesh->normals[i].Y = normals[i].Y;
            mesh->normals[i].Z = normals[i].Z;
        }
    }

    mesh->indiceCount = vertexes.size();
    mesh->indices = new GLushort[mesh->indiceCount];
    int indexNumber = 0;
    for (int i = 0; i < mesh->indiceCount; i++)
    {
        Vertex vertex = vertexes[i];

        std::map<GLushort, Vertex>::iterator it;
        for (it = indices.begin(); it != indices.end(); it++)
        {
            Vertex indicedVertex = it->second;

            if (mesh->hasTexCoords && mesh->hasNormals)
            {
                if (indicedVertex.vertice == vertex.vertice && 
                indicedVertex.texCoord == vertex.texCoord && 
                indicedVertex.normal == vertex.normal)
                {
                    mesh->indices[i] = it->first;
                    goto cnt;
                }
            }
            else if (mesh->hasTexCoords && !mesh->hasNormals)
            {
                if (indicedVertex.vertice == vertex.vertice && 
                indicedVertex.texCoord == vertex.texCoord)
                {
                    mesh->indices[i] = it->first;
                    goto cnt;
                }
            }
        }

        indices.insert(std::pair<int, Vertex>(indexNumber, vertex));
        mesh->indices[i] = indexNumber;
        indexNumber++;

        cnt:;
    }



    return mesh;
}

Essentially what it does is loads the Vertices, Texture Coordinates and Normals from a OBJ File depending on if the OBJ File does have normals and Texture Coordinates and puts them into 3 different vectors:


std::vector<Vector3> vertices;
std::vector<TexCoord> textureCoords;
std::vector<Vector3> normals;

As for loading the indices, it splits the Indices by ‘/’, put the vertice, Texture Coordinate and Normal for each Vertex in it’s own object and then passes it to another vector:


std::vector<Vertex> vertexes;

It will do that for every Vertex in the OBJ File.

To hold this vertex information for rendering, I created a mesh struct that contains all the necessary variables for holding vertices, texture coordinates, indices and so on:


struct Mesh
{
    Vector3* vertices;
    Color* colours;
    TexCoord* texCoords;
    Vector3* normals;
    GLushort* indices;

    int vertexCount;
    int colourCount;
    int texCoordCount;
    int normalCount;
    int indiceCount;

    bool hasTexCoords;
    bool hasNormals;

    ~Mesh()
    {
        delete[] vertices;
        delete[] texCoords;
        delete[] normals;
        delete[] indices;
    }
};

So at the bottom of the OBJ Loader, I’m simply looping through each vertex and putting the values into the array’s that are in the mesh.

Then for the Indices, I’m basically doing the same thing but I’m using a map to keep track of what Vertexes I’ve already used and their indice value. So I get a Vertex from the vertexes vector, loop through the map to check if the vertex is already in the map, if it is then I add the key value linked to that matching vertex which simply holds a GLushort, to the current position in the indices array in the mesh. If there are no matching vertexes in the map then I added the vertex to the map along with a indexNumber which starts at 0 and I simply increment it by one every time there is a new vertex:


indices.insert(std::pair<int, Vertex>(indexNumber, vertex));
mesh->indices[i] = indexNumber;
indexNumber++;

So that’s my whole OBJ Loader, for actually rendering the mesh, this is the code I’m using:


void SceneObject::Render()
{
    if (texture != nullptr)
    {
        glBindTexture(GL_TEXTURE_2D, texture->GetID());
    }

    if (mesh->hasTexCoords && !mesh->hasNormals)
    {
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        glVertexPointer(3, GL_FLOAT, 0, mesh->vertices);
        glTexCoordPointer(2, GL_FLOAT, 0, mesh->texCoords);
    }

    if (mesh->hasTexCoords && mesh->hasNormals)
    {
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glEnableClientState(GL_NORMAL_ARRAY);

        glVertexPointer(3, GL_FLOAT, 0, mesh->vertices);
        glTexCoordPointer(2, GL_FLOAT, 0, mesh->texCoords);
        glNormalPointer(GL_FLOAT, 0, mesh->normals);
    }

    glPushMatrix();

    glTranslatef(position.X, position.Y, position.Z);
    glRotatef(rotationAngle, rotation.X, rotation.Y, rotation.Z);

    //glInterleavedArrays(GL_T2F_N3F_V3F, 0, mesh->indices);
    glDrawElements(GL_TRIANGLES, mesh->indiceCount, GL_UNSIGNED_SHORT, mesh-
    >indices);

    //glDrawArrays(GL_TRIANGLES, 0, mesh->vertexCount);

    glPopMatrix();

    if (mesh->hasTexCoords && !mesh->hasNormals)
    {
        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }

    if (mesh->hasTexCoords && mesh->hasNormals)
    {
        glDisableClientState(GL_VERTEX_ARRAY);
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
        glDisableClientState(GL_NORMAL_ARRAY);
    }
}

From what I know, I would of thought that would load a OBJ model correctly but If I try and load this OBJ Model for example which is meant to just be a cube:


v -0.500000 -0.500000 0.500000
v 0.500000 -0.500000 0.500000
v -0.500000 0.500000 0.500000
v 0.500000 0.500000 0.500000
v -0.500000 0.500000 -0.500000
v 0.500000 0.500000 -0.500000
v -0.500000 -0.500000 -0.500000
v 0.500000 -0.500000 -0.500000

vt 0.000000 0.000000
vt 1.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000

vn 0.000000 0.000000 1.000000
vn 0.000000 1.000000 0.000000
vn 0.000000 0.000000 -1.000000
vn 0.000000 -1.000000 0.000000
vn 1.000000 0.000000 0.000000
vn -1.000000 0.000000 0.000000

f 1/1/1 2/2/1 3/3/1
f 3/3/1 2/2/1 4/4/1
f 3/1/2 4/2/2 5/3/2
f 5/3/2 4/2/2 6/4/2
f 5/4/3 6/3/3 7/2/3
f 7/2/3 6/3/3 8/1/3
f 7/1/4 8/2/4 1/3/4
f 1/3/4 8/2/4 2/4/4
f 2/1/5 8/2/5 4/3/5
f 4/3/5 8/2/5 6/4/5
f 7/1/6 1/2/6 5/3/6
f 5/3/6 1/2/6 3/4/6

It looks like this:
[ATTACH=CONFIG]1728[/ATTACH]
And I’m not sure why, can anyone help and see what I’ve done wrong? (Likely a lot)

These are the the data types I’m using by the way if you wanted to know:


#pragma once
#include <Windows.h>
#include <gl\GL.h>
#include <gl\GLU.h>
#include <math.h>

struct Vector3
{
	float X;
	float Y;
	float Z;

	Vector3()
	{
	}

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

	static Vector3 Cross(Vector3 vec1, Vector3 vec2)
	{
		Vector3 newVec = Vector3();

		newVec.X = vec1.Y * vec2.Z - vec1.Z * vec2.Y;
		newVec.Y = vec1.Z * vec2.X - vec1.X * vec2.Z;
		newVec.Z = vec1.X * vec2.Y - vec1.Y * vec2.X;

		return newVec;
	}

	static Vector3 Normalize(Vector3 vec)
	{
		float magnitudeS = (vec.X * vec.X) + (vec.Y * vec.Y) + (vec.Z * vec.Z);
		float magnitude = sqrt(magnitudeS);

		Vector3 newVec;
		newVec.X = vec.X / magnitude;
		newVec.Y = vec.Y / magnitude;
		newVec.Z = vec.Z / magnitude;

		return newVec;
	}
};

struct Vector2
{
	GLfloat X;
	GLfloat Y;
};

struct Camera2
{
	Vector3 eye;
	Vector3 center;
	Vector3 up;

	Camera2()
	{
	}
};

struct Color
{
	GLfloat R;
	GLfloat G;
	GLfloat B;
};

struct TexCoord
{
	GLfloat U;
	GLfloat V;
};

struct Vertex
{
	int vertice;
	int texCoord;
	int normal;
};

struct Mesh
{
	Vector3* vertices;
	Color* colours;
	TexCoord* texCoords;
	Vector3* normals;
	GLushort* indices;

	int vertexCount;
	int colourCount;
	int texCoordCount;
	int normalCount;
	int indiceCount;

	bool hasTexCoords;
	bool hasNormals;

	~Mesh()
	{
		delete[] vertices;
		delete[] texCoords;
		delete[] normals;
		delete[] indices;
	}
};

The main thing that you’re doing wrong is that you don’t seem to be using the index data.

Each time you associate an index with a triple of position/texcoord/normal indices, you need to copy the position/texcoord/normal data to the position/texcoord/normal arrays, so the single index refers to an element in each of the arrays. You don’t know how many vertices there will be until you’ve processed the index data, so you can’t pre-allocate the arrays (i.e. you’ll either need to use a std::vector instead of a bare array, or you’ll need to to create the index map first then have a second pass allocate and populate the arrays for the data).

Essentially, each time you do this:

you also need to do something like this:


mesh->vertices.append(vertices[vertex.vertice-1]);
mesh->normals.append(normals[vertex.normal-1]);
mesh->texCoords.append(textureCoords[vertex.texCoord-1]);

Note the “-1”, because the indices in an OBJ file start at one, but C/C++ array indices start at zero.

Also, when identifying if a particular triple of position/texcoord/normal indices has already occurred, the std::map (or std::unordered_map) should be the other way around: it should map a triple of indices to a single index. This will be much faster than iterating over the map each time.

If you use a std::tuple<int,int,int> (from C++11) or a std::pair<int,std::pair<int,int>> (from earlier versions), comparison operators will be defined automatically, but if you use unordered_map you’ll still need to specialise std::hash (boost provides specialisations for std::pair and std::tuple).

[QUOTE=GClements;1290749]The main thing that you’re doing wrong is that you don’t seem to be using the index data.

Each time you associate an index with a triple of position/texcoord/normal indices, you need to copy the position/texcoord/normal data to the position/texcoord/normal arrays, so the single index refers to an element in each of the arrays. You don’t know how many vertices there will be until you’ve processed the index data, so you can’t pre-allocate the arrays (i.e. you’ll either need to use a std::vector instead of a bare array, or you’ll need to to create the index map first then have a second pass allocate and populate the arrays for the data).

Essentially, each time you do this:

you also need to do something like this:


mesh->vertices.append(vertices[vertex.vertice-1]);
mesh->normals.append(normals[vertex.normal-1]);
mesh->texCoords.append(textureCoords[vertex.texCoord-1]);

Note the “-1”, because the indices in an OBJ file start at one, but C/C++ array indices start at zero.

Also, when identifying if a particular triple of position/texcoord/normal indices has already occurred, the std::map (or std::unordered_map) should be the other way around: it should map a triple of indices to a single index. This will be much faster than iterating over the map each time.

If you use a std::tuple<int,int,int> (from C++11) or a std::pair<int,std::pair<int,int>> (from earlier versions), comparison operators will be defined automatically, but if you use unordered_map you’ll still need to specialise std::hash (boost provides specialisations for std::pair and std::tuple).[/QUOTE]

Just wondering but what’s that vector.append? It doesn’t seem to exist.

And so I was close? Just missed a couple of things :confused:

EDIT
Thank You! :D, My OBJ loader finally works!

So I was simply putting my vertices into the array’s wrong basically? and as well for that std::map change. If I use std::tuple then how exactly am I going to have a key?. Since if it’s 3 integers for the 3 indices, surely I can’t have a key to link to the triple of position/texcoord/normal to then put in my indices array?

Sorry, I meant .push_back(). That’s what I get for doing too much Python.

Well, just the one major thing you need to do when converting OBJ to what OpenGL wants. You got half of it (identifying the mapping between OBJ vertices and OpenGL vertices), but you needed to actually put that mapping to use.

Each distinct combination of position/texcoord/normal indices needs to be a distinct vertex with a single index.

[QUOTE=codelyoko373;1290750]
and as well for that std::map change. If I use std::tuple then how exactly am I going to have a key?. Since if it’s 3 integers for the 3 indices, surely I can’t have a key to link to the triple of position/texcoord/normal to then put in my indices array?[/QUOTE]
The tuple is the key. If you could use a small integer as the key, you’d use a vector/array rather than a map.

The key for a std::map (which is typically a binary tree) can be anything for which a “less than” operation can be defined (the comparator is a template parameter, which defaults to std::less<Key>). For a std::unordered_map (typically a hash table), you need a hash function and an equality test.