.OBJ file help?

closed account (N36fSL3A)
I decided I was going to work with .obj files as a custom format was overkill for my needs.

I'm having trouble understanding this. (I'm not following a tutorial, I just looked up the format on wiki and started writing).

It's a fstream or sscanf error. When I try to read these values into my program I get some weird value, indicating that it's an uninitialized variable. I honestly have no idea what could be going wrong.

I also don't have access to a debugger, this is supposed to be a quick test with MinGW before I create a project in VC++.

This is what I exported from blender. (It's a minimal .obj loader. It only loads vertices.)

lel.obj
# www.blender.org
mtllib building.mtl
o Building_Cube
v 1.000000 -1.500000 -1.000000
v 1.000000 -1.500000 1.000000
v -1.000000 -1.500000 1.000000
v -1.000000 -1.500000 -1.000000
v 1.000000 1.500000 -0.999999
v 0.999999 1.500000 1.000001
v -1.000000 1.500000 1.000000
v -1.000000 1.500000 -1.000000
vn 0.000000 -1.000000 0.000000
vn -0.000000 1.000000 0.000000
vn 1.000000 -0.000000 0.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.000001
usemtl Material
s off
f 1//1 2//1 3//1
f 5//2 8//2 7//2
f 1//3 5//3 2//3
f 2//4 6//4 7//4
f 3//5 7//5 8//5
f 5//6 1//6 4//6
f 4//1 1//1 3//1
f 6//2 5//2 7//2
f 3//4 2//4 7//4
f 4//5 3//5 8//5
f 8//6 5//6 4//6
f 5//7 6//7 2//7


Model.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include "Texture.h"

#include <iostream>
#include <string>
#include <fstream>

#include <vector>

#include <cmath>

#ifndef _MODEL_H_
#define _MODEL_H_
struct Vertex
{
	float x, y, z;
};

struct Triangle
{
	Vertex points[3];
	Vertex normal;
};

class Mesh
{
	public:
		int LoadOBJ(const char* location);
		
		void Render(float x, float y, float z);
		
		~Mesh()
		{
			delete[] mesh;
		}
		
	private:
		Vertex CalculateNormal(float *p1, float *p2, float *p3);
	
		Triangle* mesh;
		
		Uint32 numTris;
		Uint32 numNormals;
		Uint32 numVertices;
};

/*class Model
{
	public:
		int LoadOBJ(char* location);
		
		~Model()
		{
			delete[] Mesh;
		}
		
	private:
		Mesh* model;
	
};*/

#endif//_MODEL_H_ 


Model.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#include "Model.h"

Vertex Mesh::CalculateNormal(float *p1, float *p2, float *p3) 
{ 
	float a[3], b[3], result[3]; 
	float length; 

	a[0] = p1[0] - p2[0];
	a[1] = p1[1] - p2[1];
	a[2] = p1[2] - p2[2];

	b[0] = p1[0] - p3[0]; 
	b[1] = p1[1] - p3[1];
	b[2] = p1[2] - p3[2];

	result[0] = a[1] * b[2] - b[1] * a[2]; 
	result[1] = b[0] * a[2] - a[0] * b[2]; 
	result[2] = a[0] * b[1] - b[0] * a[1]; 

	// calculate the length of the normal 
	length = (float)sqrt(result[0]*result[0] + result[1]*result[1] + result[2]*result[2]); 
	
	Vertex nrml= {result[0]/length, result[1]/length, result[2]/length};
	
	return nrml;
	// normalize and specify the normal 
	//glNormal3f(result[0]/length, result[1]/length, result[2]/length); 
}

int Mesh::LoadOBJ(const char* location)
{
	// First open fstream and load all the strings into memory
	std::ifstream file(location);
	std::vector<std::string> fileContents;
	std::vector<Vertex> verts;
	std::vector<Vertex> nrmls;
	
	numVertices = 0;
	numTris     = 0;
	
	if(file)
	{
		std::string buffer;
		while(std::getline(file, buffer))
		{
			// Add the buffer contents to our fileContents vector if it's not a comment
			// (Doing the check now reduces memory usage :D
			if(buffer[0] != '#' || buffer[0] != ' ')
			{
				fileContents.push_back(buffer);
			}
			// We really don't need an if statement...
		}
		
		if(fileContents.size() != 0)
		{
			// If the whole file wasn't all comments...
			for(unsigned int n = 0; n < fileContents.size(); n++)
			{
				if(fileContents[n].c_str()[0] == 'v' && fileContents[n].c_str()[1] == ' ')
				{
					std::cout << "Vertex\n";
					float tmpx, tmpy, tmpz;
				
					numVertices++; // Tell the compiler there is another Vertex
					
					sscanf(fileContents[n].c_str(), "v %f %f %f" ,&tmpx,&tmpy,&tmpz);
					
					Vertex tmpVert = {tmpx, tmpy, tmpz};
					verts.push_back(tmpVert);
				}
				
				/*else if(fileContents[n].c_str()[0] == 'v' && fileContents[n].c_str()[1] == 'n' && fileContents[n].c_str()[2] == ' ')
				{
					std::cout << "Normal\n";
					float tmpx, tmpy, tmpz;
				
					numNormals++; // Tell the program there is another normal
					
					sscanf(fileContents[n].c_str(), "vn %f %f %f" ,&tmpx,&tmpy,&tmpz);
					
					Vertex tmpVert = {tmpx, tmpy, tmpz};
					nrmls.push_back(tmpVert);
				}*/
				
				/*else if((*cm_coordinates[i])[0] == 'f')  
				{
					int a,b,c,d,e;
					if(count(cm_coordinates[i]->begin(),cm_coordinates[i]->end(), ' ') == 3)
					{
						sscanf(cm_coordinates[i]->c_str(), "f %d//%d %d//%d %d//%d" ,&a, &b, &c, &b, &d, &b);
						cm_faces.push_back(new face(b, a, c, d));
					}
					else
					{
						sscanf(cm_coordinates[i]->c_str(), "f %d//%d %d//%d %d//%d %d//%d" ,&a, &b, &c, &b, &d, &b, &e, &b);
						cm_faces.push_back(new face(b, a, c, d, e));   
					}
				}*/
			}
			
			// Take the contents out of memory, we don't need them any more
			/*for(unsigned int s = 0; s < fileContents.size(); s++)
			{
				fileContents.pop_back();
			}*/
			
			// Now that we got it all loaded, lets put the data together
			
			// If the number of Vertices is divisible by 3, it's a valid format as it's
			// a triangle. If not, it's a quad based (Assuming), and the modeller will
			// be forced to convert it to a triangle based model. (Easy with blender)
			
			if((numVertices % 3) == 0)
			{
				std::cout << "Triangular! :D\n";
				numTris = (numVertices / 3); // Set the number of triangles
				
				std::cout << numTris << "\n";
				mesh = new Triangle[numTris]; // Allocate memory for the triangles
				
				// Put all vertices inside the newly allocated triangles
				for(unsigned int n = 0; n < numVertices;)
				{
					for(unsigned int m = 0; m < numTris; m++)
					{
						mesh[m].points[0] = verts[n];
						n++;
						mesh[m].points[1] = verts[n];
						n++;
						mesh[m].points[2] = verts[n];
						n++;
					}
				}
				
				for(unsigned int t = 0; t < numTris; t++)
				{
					// Get arrays to calculate normal
					float p1[] = {mesh[t].points[0].x, mesh[t].points[0].y, mesh[t].points[0].z};
					float p2[] = {mesh[t].points[1].x, mesh[t].points[1].y, mesh[t].points[1].z};
					float p3[] = {mesh[t].points[2].x, mesh[t].points[2].y, mesh[t].points[2].z};
					
					// Calculate the normal
					mesh[t].normal = CalculateNormal(p1, p2, p3);
				}
				
				// Now all calculations are done, the model should be loaded
				std::cout << "Model successfully loaded.\n";
			}
			
			else
			{
				// (I don't like it when people have quad based models :P)
				std::cout << "Failure to load model at \"" << location << "\". Not triangle based.\n";
				return 3;
			}
		}
		
		else
		{
			std::cout << "File \"" << location << "\" Was empty... Failure to load\n";
			return 2;
		}
	}
	
	else
	{
		std::cout << "Failure opening file at \"" << location <<"\".";
		return 1;
	}
}

void Mesh::Render(float x, float y, float z)
{	
	glLoadIdentity();
	glTranslatef(x, y, z);
	
	for(Uint32 n = 0; n < numTris; n++)
	{
		glBegin(GL_TRIANGLES);
			// Normals
			glNormal3f(mesh[n].normal.x, mesh[n].normal.y, mesh[n].normal.z);
			
			std::cout << "=======================================================\n";
			std::cout << "Normal - " << mesh[n].normal.x << mesh[n].normal.y << mesh[n].normal.z << "\n";
			std::cout << "Triangle(" << n << ")\n";
			std::cout << mesh[n].points[0].x << " " << mesh[n].points[0].y << " " << mesh[n].points[0].z << "\n"
			          << mesh[n].points[1].x << " " << mesh[n].points[1].y << " " << mesh[n].points[1].z << "\n"
					  << mesh[n].points[2].x << " " << mesh[n].points[2].y << " " << mesh[n].points[2].z << "\n";
					  
			//std::cout << "====================\n";
			
			// Triangular points
			glVertex3f(mesh[n].points[0].x, mesh[n].points[0].y, mesh[n].points[0].z);
			glVertex3f(mesh[n].points[1].x, mesh[n].points[1].y, mesh[n].points[1].z);
			glVertex3f(mesh[n].points[2].x, mesh[n].points[2].y, mesh[n].points[2].z);
		glEnd();
	}
}
Last edited on
I'm having trouble understanding this. (I'm not following a tutorial, I just looked up the format on wiki and started writing).


The reason you're having trouble understanding it is because you looked up the format on wiki and just started writing, rather than attempting to understand the format. The fact that there are 8 geometric vertices in the file and 7 normals should've probably given you a clue that you were missing some information.

Visit: http://www.martinreddy.net/gfx/3d/OBJ.spec
Skip down to "Referencing groups of vertices." You'll see that it works much like a vertex buffer object in concert with an element buffer object in OpenGL.

Last edited on
closed account (N36fSL3A)
I think I understand the concept, but the coding part not so much.
closed account (N36fSL3A)
-BUMP-

Is there a way to make Blender just output all the triangle's vertices even if they are the same as anothers? I really don't feel like changing my code.

My game isn't meant to be edited anyway.
Last edited on
closed account (o1vk4iN6)
Why not have error checks be a single line and have the exit right there to avoid all those unnecessary tabs...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
int Mesh::LoadOBJ(const char* location)
{
	// First open fstream and load all the strings into memory
	std::ifstream file(location);
	std::vector<std::string> fileContents;
	std::vector<Vertex> verts;
	std::vector<Vertex> nrmls;
	
	numVertices = 0;
	numTris     = 0;
	
	if(!file)
	{
		std::cout << "Failure opening file at \"" << location <<"\".";
		return 1;
	}
		
	std::string buffer;
	while(std::getline(file, buffer))
	{
		// Add the buffer contents to our fileContents vector if it's not a comment
		// (Doing the check now reduces memory usage :D
		if(buffer[0] != '#' || buffer[0] != ' ')
		{
			fileContents.push_back(buffer);
		}
		// We really don't need an if statement...
	}
	
	if(fileContents.size() == 0)
	{
		std::cout << "File \"" << location << "\" Was empty... Failure to load\n";
		return 2;
	}

	// If the whole file wasn't all comments...
	for(unsigned int n = 0; n < fileContents.size(); n++)
	{
		if(fileContents[n].c_str()[0] == 'v' && fileContents[n].c_str()[1] == ' ')
		{
			std::cout << "Vertex\n";
			float tmpx, tmpy, tmpz;
		
			numVertices++; // Tell the compiler there is another Vertex
			
			sscanf(fileContents[n].c_str(), "v %f %f %f" ,&tmpx,&tmpy,&tmpz);
			
			Vertex tmpVert = {tmpx, tmpy, tmpz};
			verts.push_back(tmpVert);
		}
		
		/*else if(fileContents[n].c_str()[0] == 'v' && fileContents[n].c_str()[1] == 'n' && fileContents[n].c_str()[2] == ' ')
		{
			std::cout << "Normal\n";
			float tmpx, tmpy, tmpz;
		
			numNormals++; // Tell the program there is another normal
			
			sscanf(fileContents[n].c_str(), "vn %f %f %f" ,&tmpx,&tmpy,&tmpz);
			
			Vertex tmpVert = {tmpx, tmpy, tmpz};
			nrmls.push_back(tmpVert);
		}*/
		
		/*else if((*cm_coordinates[i])[0] == 'f')  
		{
			int a,b,c,d,e;
			if(count(cm_coordinates[i]->begin(),cm_coordinates[i]->end(), ' ') == 3)
			{
				sscanf(cm_coordinates[i]->c_str(), "f %d//%d %d//%d %d//%d" ,&a, &b, &c, &b, &d, &b);
				cm_faces.push_back(new face(b, a, c, d));
			}
			else
			{
				sscanf(cm_coordinates[i]->c_str(), "f %d//%d %d//%d %d//%d %d//%d" ,&a, &b, &c, &b, &d, &b, &e, &b);
				cm_faces.push_back(new face(b, a, c, d, e));   
			}
		}*/
	}
	
	// Take the contents out of memory, we don't need them any more
	/*for(unsigned int s = 0; s < fileContents.size(); s++)
	{
		fileContents.pop_back();
	}*/
	
	// Now that we got it all loaded, lets put the data together
	
	// If the number of Vertices is divisible by 3, it's a valid format as it's
	// a triangle. If not, it's a quad based (Assuming), and the modeller will
	// be forced to convert it to a triangle based model. (Easy with blender)
	
	if((numVertices % 3) != 0)
	{
		// (I don't like it when people have quad based models :P)
		std::cout << "Failure to load model at \"" << location << "\". Not triangle based.\n";
		return 3;
	}

	std::cout << "Triangular! :D\n";
	numTris = (numVertices / 3); // Set the number of triangles
	
	std::cout << numTris << "\n";
	mesh = new Triangle[numTris]; // Allocate memory for the triangles
	
	// Put all vertices inside the newly allocated triangles
	for(unsigned int n = 0; n < numVertices;)
	{
		for(unsigned int m = 0; m < numTris; m++)
		{
			mesh[m].points[0] = verts[n];
			n++;
			mesh[m].points[1] = verts[n];
			n++;
			mesh[m].points[2] = verts[n];
			n++;
		}
	}
	
	for(unsigned int t = 0; t < numTris; t++)
	{
		// Get arrays to calculate normal
		float p1[] = {mesh[t].points[0].x, mesh[t].points[0].y, mesh[t].points[0].z};
		float p2[] = {mesh[t].points[1].x, mesh[t].points[1].y, mesh[t].points[1].z};
		float p3[] = {mesh[t].points[2].x, mesh[t].points[2].y, mesh[t].points[2].z};
		
		// Calculate the normal
		mesh[t].normal = CalculateNormal(p1, p2, p3);
	}
	
	// Now all calculations are done, the model should be loaded
	std::cout << "Model successfully loaded.\n";

	
}


You can now also see you are missing a return for when the model was successfully loaded.

Why are you storing the model as triangles ? Not to mention you still have a 3D vector named as a vertex.
closed account (N36fSL3A)
Why are you storing the model as triangles ?
They're faster than quads

Not to mention you still have a 3D vector named as a vertex.
About that...
closed account (o1vk4iN6)
They're faster than quads

Lol...

Not what I meant but quite ironic you are saying that while using glbegin/glend. Normally you store them as vertices and have triangles defined as 3 indices to those vertices.
Last edited on
closed account (N36fSL3A)
Not what I meant but quite ironic you are saying that while using glbegin/glend. Normally you store them as vertices and have triangles defined as 3 indices to those vertices.
I'm using the fixed function pipeline for older-machine support.

Is there a way to export all the triangles individually though?
I'm using the fixed function pipeline for older-machine support.

You're using the fixed function pipeline because you can't be bothered to inform yourself.


Is there a way to export all the triangles individually though?

That would be a great question for a blender forum. You have all the information you need to generate them yourself from the .obj file.
closed account (o1vk4iN6)
I'm using the fixed function pipeline for older-machine support.

So you want to support cards from pre 2000 ? I didn't even say fixed function pipeline, you aren't loading the data onto the GPU so every time you draw you have to transfer the data. If you plan to support 13 year old cards you are going to have huge performance issues the way you are using glbegin/glend.
Last edited on
closed account (N36fSL3A)
I'm using OpenGL 2.1. I don't know how to use GSL

you aren't loading the data onto the GPU so every time you draw you have to transfer the data
Please explain. Would a list fix this problem?
Topic archived. No new replies allowed.