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;
}
};