Wavefront OBJ file reading - Polygons connect incorrectly

Started by
4 comments, last by Helixirr 11 years, 3 months ago

Hi, folks!

I have tried to create a 3D model class called Model3, which will inherit from my base class Model. This Model3-class supports only Wavefront OBJ files at the moment and I'm having trouble setting up correct data for OpenGL to process. I try to get rid of the deprecated OpenGL functions and use vertex array objects and vertex buffers objects instead to accomplish my goal to display a 3D model on screen.

The problem is rather simple to describe: polygons are formed incorrectly.

This is how I see the model in Blender (in which I also did the model):
[url="http://imageshack.us/photo/my-images/838/timg.png/"][url="http://imageshack.us/photo/my-images/838/timg.png/"][url="http://imageshack.us/photo/my-images/838/timg.png/"]http://imageshack.us/photo/my-images/838/timg.png/[/url][/url][/url]

And this is what ends up looking in my program:
[url="http://imageshack.us/photo/my-images/546/tim2i.png/"][url="http://imageshack.us/photo/my-images/546/tim2i.png/"][url="http://imageshack.us/photo/my-images/546/tim2i.png/"]http://imageshack.us/photo/my-images/546/tim2i.png/[/url][/url][/url]

Here's Model3 class definition:


/// ----------------------------------
    /// @class  	Model3
    /// @brief  	Represents a 3D model.
	/// @typedef	Model3D
    /// ----------------------------------
    typedef class Model3 : public Model{
    public:

		/// Enumerations:
		enum class FormatFile : unsigned int{
			FORMATFILE_3DS,
			FORMATFILE_OBJ
		};

        /// Constructors & destructors:
        Model3(void);
        Model3(Model3 const& model) = default;
        Model3(Model3&& model) = default;
		~Model3(void);

		/// Member functions:
		Model3& set_data(Model3::FormatFile const& format_file, std::string const& name_file);
		Model3& set_data(Model3::FormatFile const& format_file, std::string const&& name_file);
		Model3& set_data(Model3::FormatFile&& format_file, std::string const& name_file);
		Model3& set_data(Model3::FormatFile&& format_file, std::string const&& name_file);
		void show(void) const;

		/// Member functions (overloaded operators):
		Model3& operator=(Model3 const& model);
		Model3& operator=(Model3&& model);

    private:
        /// Member data:
		std::vector<Helixirr::Vector3<float>> _m_voDataFace[3]; // Element 0 contains vertex coordinates, element 1 contains texture coordinates, element 2 contains vertex normals.
		std::vector<Helixirr::Vector3<float>> _m_voDataVertex[3]; // Element 0 contains vertex coordinates, element 1 contains texture coordinates, element 2 contains vertex normals.
		unsigned int _m_uiIdArrayVertex, _m_uiBufferVertex;
	}Model3D;

Here are implementations of functions set_data and show:


/// Member functions:
Model3& Model3::set_data(Model3::FormatFile const& format_file, std::string const& name_file){ /* UNFINISHED! */
	std::ifstream __file(name_file);

	// 3D model loading lambdas:
	auto const __load_3ds = [&](void){
		/* UNFINISHED! */
	};
	auto const __load_obj = [&](void){ /* WORKS HALF RIGHT AND MORE FUNCTIONALITY IS REQUIRED! */
		auto const __read_data_vertices = [](std::string const& string, float coordinates[], unsigned int const&& amount_elements, unsigned int&& index_begin){
			// Variables used in the following for loop:
			// index_begin = end index for string substraction operation
			// __i = index of the temporary float array
			// __j = start index for string substraction operation
			for(unsigned int __i = 0, __j = index_begin; __i != amount_elements; ++__j){
				if(string[__j] == ' '){ // If the "end character" for floating number is detected:
					coordinates[__i++] = atof(string.substr(index_begin, ++__j).c_str());
					index_begin = __j;
				}
			}
		};

		std::string __line;
		clock_t init = clock();
		while(std::getline(__file, __line)){ // For each line in the requested file:
			switch(__line[0]){ // Check out the first letter of a line:
				case 'f':{
					// Read through the whole line ignoring all the insignificant characters (like '\n'):
					for(unsigned int __i = 2, __j = 2, __k = 0; __j != __line.size() - 1; ++__j){
						if(__line[__j] == '/'){
							_m_voDataFace[__k].push_back(_m_voDataVertex[__k][atoi(__line.substr(__i, ++__j).c_str())]);
							__i = __j;
							++__k;
						}
						else if(__line[__j] == ' '){
							++__j;
							__i = __j;
							__k = 0;
						}
					}
					continue;
				}
				case 'v': // If a requested line contains some sort of vertex data:
					if(__line[1] == ' '){ // If the requested line contains vertex coordinates:
						float __coordinates[3] = {0.0f}; // A temporary float array to store vertex coordinates.
						__read_data_vertices(__line, __coordinates, 3, 2);
						_m_voDataVertex[0].push_back(Helixirr::Vector3<float>(std::move(__coordinates[0]), std::move(__coordinates[1]), std::move(__coordinates[2])));
					}
					else if(__line[1] == 'n'){ // If the requested line contains vertex normals:
						float __coordinates[3] = {0.0f}; // A temporary float array to store vertex normals.
						__read_data_vertices(__line, __coordinates, 3, 3);
						_m_voDataVertex[2].push_back(Helixirr::Vector3<float>(std::move(__coordinates[0]), std::move(__coordinates[1]), std::move(__coordinates[2])));
					}
					else if(__line[1] == 't'){ // If the requested line contains UV coordinates:
						float __coordinates[2] = {0.0f}; // A temporary float array to store UV coordinates.
						__read_data_vertices(__line, __coordinates, 2, 3);
						_m_voDataVertex[1].push_back(Helixirr::Vector3<float>(std::move(__coordinates[0]), std::move(__coordinates[1]), 0.0f));
					}
					continue;
				default:
					continue;
			}
		}
		printf("OBJ file loading - elapsed time: %f\n", static_cast<double>(clock() - init) / static_cast<double>(CLOCKS_PER_SEC));
	};

	// Detect requested file format:
	switch(format_file){
		case Model3::FormatFile::FORMATFILE_3DS:
			__load_3ds();
			break;
		case Model3::FormatFile::FORMATFILE_OBJ:
			__load_obj();
			break;
		default:
			break;
	}

	// Create and bind a vertex array:
	if(_m_uiIdArrayVertex == 0)
		glGenVertexArrays(1, &_m_uiIdArrayVertex);
	glBindVertexArray(_m_uiIdArrayVertex);

	// Create and bind a vertex buffer object:
	if(_m_uiBufferVertex == 0)
		glGenBuffers(1, &_m_uiBufferVertex);
	glBindBuffer(GL_ARRAY_BUFFER, _m_uiBufferVertex);

	// Insert vertex data:
	glBufferData(GL_ARRAY_BUFFER, sizeof(_m_voDataFace[0][0]) * _m_voDataFace[0].size(), &_m_voDataFace[0][0], GL_STATIC_DRAW);

	return *this;
}
Model3& Model3::set_data(Model3::FormatFile const& format_file, std::string const&& name_file){
	return this->set_data(format_file, name_file);
}
Model3& Model3::set_data(Model3::FormatFile&& format_file, std::string const& name_file){
	return this->set_data(format_file, name_file);
}
Model3& Model3::set_data(Model3::FormatFile&& format_file, std::string const&& name_file){
	return this->set_data(format_file, name_file);
}
void Model3::show(void) const{
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, _m_uiBufferVertex);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

	glEnable(GL_POINT_SMOOTH);
	glPointSize(2.0f);

	glDrawArrays(GL_TRIANGLES, 0, _m_voDataFace[0].size() + 1);
	glDisableVertexAttribArray(0);
}


I don't understand what have I done wrong. Can anybody help me?

Advertisement

Looks like you're not converting the vertex indices. The obj-file has 1-based indices but std::vector has 0-based indices.

Looks like you're not converting the vertex indices. The obj-file has 1-based indices but std::vector has 0-based indices.

Thanks for the reply. Now, what does this mean? What are these vertex indices anyway? Am I reading OBJ file incorrectly? How do I practically do this conversion?

In C++, an index is 0-based which means that the first element of an array has index 0, the second element has index 1, the third element has index 2, and so on. The OBJ file is 1-based, which means that the first vertex has index 1, the second vertex has index 2, the third vertex has index 3, and so on. Therefore, you cannot read an index from the OBJ file and use it directly to reference a C++ array.

The indices are what you read from the f-lines. For example, "f 1 2 3" means: the face is made up of three vertices (because there are three values), and the vertices are the vertex with index 1, the vertex with index 2, and the vertex with index 3.

It means read the index from the .OBJ file, subtract one, and use that value in your std::vector.

L. Spiro

PS: Heh, and here I am always being so vocal against spoon-feeding. tongue.png

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid

In C++, an index is 0-based which means that the first element of an array has index 0, the second element has index 1, the third element has index 2, and so on. The OBJ file is 1-based, which means that the first vertex has index 1, the second vertex has index 2, the third vertex has index 3, and so on. Therefore, you cannot read an index from the OBJ file and use it directly to reference a C++ array.

The indices are what you read from the f-lines. For example, "f 1 2 3" means: the face is made up of three vertices (because there are three values), and the vertices are the vertex with index 1, the vertex with index 2, and the vertex with index 3.

It means read the index from the .OBJ file, subtract one, and use that value in your std::vector.

L. Spiro

PS: Heh, and here I am always being so vocal against spoon-feeding. tongue.png

Now, after reading all this, I feel so stupid not to have realized, what I did wrong. Oh boy... rolleyes.gif

Fortunately, it works now. Thank you, God and thank you all. smile.png

Here is the working piece of code with a slight modification dedicated for those who are also interested in reading an OBJ file and putting OBJ file data into OpenGL. Please, have a look:


/// ----------------------------------
    /// @class  	Model3
    /// @brief  	Represents a 3D model.
	/// @typedef	Model3D
    /// ----------------------------------
    typedef class Model3 : public Model{
    public:

		/// Enumerations:
		enum class FormatFile : unsigned int{
			FORMATFILE_3DS,
			FORMATFILE_OBJ
		};

        /// Constructors & destructors:
        Model3(void);
        Model3(Model3 const& model) = default;
        Model3(Model3&& model) = default;
		~Model3(void);

		/// Member functions:
		Model3& set_data(Model3::FormatFile const& format_file, std::string const& name_file);
		Model3& set_data(Model3::FormatFile const& format_file, std::string const&& name_file);
		Model3& set_data(Model3::FormatFile&& format_file, std::string const& name_file);
		Model3& set_data(Model3::FormatFile&& format_file, std::string const&& name_file);
		void show(void) const;

		/// Member functions (overloaded operators):
		Model3& operator=(Model3 const& model);
		Model3& operator=(Model3&& model);

    private:
        /// Member data:
		std::vector<Helixirr::Vector3<float>> _m_voDataFace[3]; // Element 0 contains vertex coordinates, element 1 contains texture coordinates, element 2 contains vertex normals.
		std::vector<Helixirr::Vector3<float>> _m_voDataVertex[3]; // Element 0 contains vertex coordinates, element 1 contains texture coordinates, element 2 contains vertex normals.
		unsigned int _m_uiIdArrayVertex, _m_uiBufferVertex;
	}Model3D;

/// Member functions:
Model3& Model3::set_data(Model3::FormatFile const& format_file, std::string const& name_file){ /* UNFINISHED! */
	std::ifstream __file(name_file);

	// 3D model loading lambdas:
	auto const __load_3ds = [&](void){
		/* UNFINISHED! */
	};
	auto const __load_obj = [&](void){ /* WORKS HALF RIGHT AND MORE FUNCTIONALITY IS REQUIRED! */
		auto const __read_data_vertices = [](std::string const& string, float coordinates[], unsigned int const&& amount_elements, unsigned int&& index_begin){
			// Variables used in the following for loop:
			// index_begin = end index for string substraction operation
			// __i = index of the temporary float array
			// __j = start index for string substraction operation
			for(unsigned int __i = 0, __j = index_begin; __i != amount_elements; ++__j){
				if(string[__j] == ' '){ // If the "end character" for floating number is detected:
					coordinates[__i++] = atof(string.substr(index_begin, ++__j).c_str());
					index_begin = __j;
				}
			}
		};

		std::string __line;
		clock_t init = clock();
		while(std::getline(__file, __line)){ // For each line in the requested file:
			switch(__line[0]){ // Check out the first letter of a line:
				case 'f':{
					// Read through the whole line ignoring all the insignificant characters (like '\n'):
					for(unsigned int __i = 2, __j = 2, __k = 0; __j != __line.size() - 1; ++__j){
						if(__line[__j] == '/'){
							_m_voDataFace[__k].push_back(_m_voDataVertex[__k][atoi(__line.substr(__i, ++__j).c_str()) - 1]);
							__i = __j;
							++__k;
						}
						else if(__line[__j] == ' '){
							++__j;
							__i = __j;
							__k = 0;
						}
					}
					continue;
				}
				case 'v': // If a requested line contains some sort of vertex data:
					if(__line[1] == ' '){ // If the requested line contains vertex coordinates:
						float __coordinates[3] = {0.0f}; // A temporary float array to store vertex coordinates.
						__read_data_vertices(__line, __coordinates, 3, 2);
						_m_voDataVertex[0].push_back(Helixirr::Vector3<float>(std::move(__coordinates[0]), std::move(__coordinates[1]), std::move(__coordinates[2])));
					}
					else if(__line[1] == 'n'){ // If the requested line contains vertex normals:
						float __coordinates[3] = {0.0f}; // A temporary float array to store vertex normals.
						__read_data_vertices(__line, __coordinates, 3, 3);
						_m_voDataVertex[2].push_back(Helixirr::Vector3<float>(std::move(__coordinates[0]), std::move(__coordinates[1]), std::move(__coordinates[2])));
					}
					else if(__line[1] == 't'){ // If the requested line contains UV coordinates:
						float __coordinates[2] = {0.0f}; // A temporary float array to store UV coordinates.
						__read_data_vertices(__line, __coordinates, 2, 3);
						_m_voDataVertex[1].push_back(Helixirr::Vector3<float>(std::move(__coordinates[0]), std::move(__coordinates[1]), 0.0f));
					}
					continue;
				default:
					continue;
			}
		}
		printf("OBJ file loading - elapsed time: %f\n", static_cast<double>(clock() - init) / static_cast<double>(CLOCKS_PER_SEC));
	};

	// Detect requested file format:
	switch(format_file){
		case Model3::FormatFile::FORMATFILE_3DS:
			__load_3ds();
			break;
		case Model3::FormatFile::FORMATFILE_OBJ:
			__load_obj();
			break;
		default:
			break;
	}

	// Create and bind a vertex array:
	if(_m_uiIdArrayVertex == 0)
		glGenVertexArrays(1, &_m_uiIdArrayVertex);
	glBindVertexArray(_m_uiIdArrayVertex);

	// Create and bind a vertex buffer object:
	if(_m_uiBufferVertex == 0)
		glGenBuffers(1, &_m_uiBufferVertex);
	glBindBuffer(GL_ARRAY_BUFFER, _m_uiBufferVertex);

	// Insert vertex data:
	glBufferData(GL_ARRAY_BUFFER, sizeof(_m_voDataFace[0][0]) * _m_voDataFace[0].size(), &_m_voDataFace[0][0], GL_STATIC_DRAW);

	return *this;
}
Model3& Model3::set_data(Model3::FormatFile const& format_file, std::string const&& name_file){
	return this->set_data(format_file, name_file);
}
Model3& Model3::set_data(Model3::FormatFile&& format_file, std::string const& name_file){
	return this->set_data(format_file, name_file);
}
Model3& Model3::set_data(Model3::FormatFile&& format_file, std::string const&& name_file){
	return this->set_data(format_file, name_file);
}
void Model3::show(void) const{
	glEnableVertexAttribArray(0);
	glBindBuffer(GL_ARRAY_BUFFER, _m_uiBufferVertex);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

	glEnable(GL_POINT_SMOOTH);
	glPointSize(2.0f);

	glDrawArrays(GL_TRIANGLES, 0, _m_voDataFace[0].size() + 1);
	glDisableVertexAttribArray(0);
}


This topic is closed to new replies.

Advertisement