Storing "Model" format
Hi all Im new to this site and have ordered the CD. Ive gone thru alot of the tutorials (THANKS NEHE!!!) and have openGL running and drawing fine. My question is (and forgive me if it already explained somewhere). "What is the most efficent method of Storing and Drawing a triangulated model" I want my code to be OOP and have a class "model". I initially want to be dealing with imported.obj (alias wavefront) files. Im thinking some thing on the lines of (not in code yet..just formalising idea) model::model(){ setup stuff here } model::importOBJ(){ .obj parsing stuff here } Now would this be efficient: model = array of faces (triangles) faces = x3 array of vertices vertices = 3 floats So I need to parse the obj file and then order it like above. Now. say I make model = faces[MAXFACES] This is a finite amount of faces each model can have. Isnt this a problem? If I make MAXFACES to small then the face count is restricted and may not be able to import some models. If MAXFACES is large (Million+), it is a waste of allocated memory when dealing with small models of 10s,100s,1000s faces etc. Would it better to make faces a linked list and when displaying it just traverses the list? This makes it inifitely scalable...but then referencing each face may be a problem. Could any one share their opinion and experience with me or if this has been covered already point me to the correct place Many thanks -LW
-------------------LordsWarrior-------------------
Hiya,
As an addition. .objs have smoothing groups etc..can this be taken into regard in openGL also. Is ther an example somewhere with how to draw smoothed polygons. Soft normals etc
Thanks
-P
As an addition. .objs have smoothing groups etc..can this be taken into regard in openGL also. Is ther an example somewhere with how to draw smoothed polygons. Soft normals etc
Thanks
-P
-------------------LordsWarrior-------------------
If you are familiar with STL, I wrote an obj file loader/saver class which makes heavy use of the set container for fast index lookup...
It should load pretty much any obj file, unless you run out of memory that is. I've tested this with obj files that were around 400MB.
objread.h:
objread.cpp:
It should load pretty much any obj file, unless you run out of memory that is. I've tested this with obj files that were around 400MB.
objread.h:
#include <set>using std::set;#include <fstream>using std::ifstream;using std::ofstream;#include <string>using std::string;#include <iostream>using std::cout;using std::endl;#include <vector>using std::vector;#include <sstream>using std::istringstream;#include <cctype>using std::tolower;#include <stdexcept>using std::runtime_error;#include <cmath>using std::sqrt;#include <iomanip>using std::setiosflags;using std::setprecision;#include <ios>string lower_string(const string &src_string){ string temp = src_string; for(string::iterator i = temp.begin(); i != temp.end(); i++) *i = tolower(*i); return temp;}struct indexed_vertex{ double x,y,z; size_t index; bool operator<(const indexed_vertex &right) const { if(right.index > index) return true; return false; } bool operator==(const indexed_vertex &right) const { if(right.index == index) return true; else return false; } indexed_vertex& operator=(const indexed_vertex &right) { x = right.x; y = right.y; z = right.z; return *this; }};struct triangle{ size_t vertex_indices[3]; size_t vertex_normal_indices[3]; bool operator<(const triangle &right) const { if(right.vertex_indices[0] > vertex_indices[0]) return true; else if(right.vertex_indices[0] < vertex_indices[0]) return false; if(right.vertex_indices[1] > vertex_indices[1]) return true; else if(right.vertex_indices[1] < vertex_indices[1]) return false; if(right.vertex_indices[2] > vertex_indices[2]) return true; else if(right.vertex_indices[2] < vertex_indices[2]) return false; return false; } bool operator==(const triangle &right) const { if( right.vertex_indices[0] == vertex_indices[0] && right.vertex_indices[1] == vertex_indices[1] && right.vertex_indices[2] == vertex_indices[2] ) { return true; } if( right.vertex_indices[2] == vertex_indices[0] && right.vertex_indices[0] == vertex_indices[1] && right.vertex_indices[1] == vertex_indices[2] ) { return true; } if( right.vertex_indices[1] == vertex_indices[0] && right.vertex_indices[2] == vertex_indices[1] && right.vertex_indices[0] == vertex_indices[2] ) { return true; } return false; } triangle& operator=(const triangle &right) { vertex_indices[0] = right.vertex_indices[0]; vertex_indices[1] = right.vertex_indices[1]; vertex_indices[2] = right.vertex_indices[2]; return *this; }};class obj_mesh{public: set<indexed_vertex> vertices; set<indexed_vertex> vertex_normals; set<triangle> triangles; void ReadFile(const string &in_obj_filename) { vertices.clear(); vertex_normals.clear(); triangles.clear(); ParseObjFile(in_obj_filename); ValidateIndices(); } void WriteFile(const string &out_obj_filename) { ofstream out(out_obj_filename.c_str()); if(out.fail()) throw runtime_error("Could not create/open file."); out << setiosflags(std::ios::fixed); out << setprecision(18); out << "o obj_mesh\n"; out << "g obj_mesh\n"; out << "\n# " << vertices.size() << " vertices" << "\n"; for(set<indexed_vertex>::const_iterator i = vertices.begin(); i != vertices.end(); i++) out << "v " << (*i).x << " " << (*i).y << " " << (*i).z << "\n"; out << "\n# " << vertex_normals.size() << " vertex normals" << "\n"; for(set<indexed_vertex>::const_iterator i = vertex_normals.begin(); i != vertex_normals.end(); i++) out << "vn " << (*i).x << " " << (*i).y << " " << (*i).z << "\n"; out << "\n# " << triangles.size() << " triangles" << "\n"; for(set<triangle>::const_iterator i = triangles.begin(); i != triangles.end(); i++) out << "f " << (*i).vertex_indices[0]+1 << "//" << (*i).vertex_indices[0]+1 << " " << (*i).vertex_indices[1]+1 << "//" << (*i).vertex_indices[1]+1 << " " << (*i).vertex_indices[2]+1 << "//" << (*i).vertex_indices[2]+1 << " " << "\n"; out.close(); }protected: void ParseObjLine(const string &line) { if(line == "") return; istringstream in(lower_string(line)); long unsigned int token_index = 0; string token(""); #define PROCESS_VERTEX 0 #define PROCESS_VERTEX_NORMAL 1 #define PROCESS_TRIANGLE 2 unsigned char processing_type = 0; indexed_vertex temp_vertex; indexed_vertex temp_vertex_normal; triangle temp_triangle; while(in >> token && token_index < 4) { if(token_index == 0) { if(token == "v") processing_type = PROCESS_VERTEX; else if(token == "vn") processing_type = PROCESS_VERTEX_NORMAL; else if(token == "f") processing_type = PROCESS_TRIANGLE; else return; } else if(token_index == 1) { if(processing_type == PROCESS_VERTEX) { temp_vertex.x = atof(token.c_str()); } else if(processing_type == PROCESS_VERTEX_NORMAL) { temp_vertex_normal.x = atof(token.c_str()); } else if(processing_type == PROCESS_TRIANGLE) { size_t pos = token.find_first_of("/"); if(pos != string::npos) token == token.substr(0, pos); istringstream local_in; local_in.str(token); local_in >> temp_triangle.vertex_indices[0]; temp_triangle.vertex_normal_indices[0] = temp_triangle.vertex_indices[0]; } } else if(token_index == 2) { if(processing_type == PROCESS_VERTEX) { temp_vertex.y = atof(token.c_str()); } else if(processing_type == PROCESS_VERTEX_NORMAL) { temp_vertex_normal.y = atof(token.c_str()); } else if(processing_type == PROCESS_TRIANGLE) { size_t pos = token.find_first_of("/"); if(pos != string::npos) token == token.substr(0, pos); istringstream local_in; local_in.str(token); local_in >> temp_triangle.vertex_indices[1]; temp_triangle.vertex_normal_indices[1] = temp_triangle.vertex_indices[1]; } } else if(token_index == 3) { if(processing_type == PROCESS_VERTEX) { temp_vertex.z = atof(token.c_str()); } else if(processing_type == PROCESS_VERTEX_NORMAL) { temp_vertex_normal.z = atof(token.c_str()); } else if(processing_type == PROCESS_TRIANGLE) { size_t pos = token.find_first_of("/"); if(pos != string::npos) token == token.substr(0, pos); istringstream local_in; local_in.str(token); local_in >> temp_triangle.vertex_indices[2]; temp_triangle.vertex_normal_indices[2] = temp_triangle.vertex_indices[2]; } } token_index++; } if(token_index != 4) return; if(processing_type == PROCESS_VERTEX) { temp_vertex.index = static_cast<long unsigned int>(vertices.size()); vertices.insert(temp_vertex); } else if(processing_type == PROCESS_VERTEX_NORMAL) { temp_vertex_normal.index = static_cast<long unsigned int>(vertex_normals.size()); vertex_normals.insert(temp_vertex_normal); } else if(processing_type == PROCESS_TRIANGLE) { temp_triangle.vertex_indices[0]--; temp_triangle.vertex_indices[1]--; temp_triangle.vertex_indices[2]--; temp_triangle.vertex_normal_indices[0]--; temp_triangle.vertex_normal_indices[1]--; temp_triangle.vertex_normal_indices[2]--; triangles.insert(temp_triangle); } } void ParseObjFile(const string &in_obj_filename) { ifstream in(in_obj_filename.c_str()); if(in.fail()) throw runtime_error("Could not open file."); string line(""); while(getline(in, line)) ParseObjLine(line); if(in.fail() && !in.eof()) throw runtime_error("Could not fully read file."); in.close(); } void ValidateIndices() { if(vertices.size() != vertex_normals.size()) GenerateVertexNormalsFromVertices(); long unsigned int vertex_size = static_cast<long unsigned int>(vertices.size()); long unsigned int vertex_normals_size = static_cast<long unsigned int>(vertex_normals.size()); for(set<triangle>::const_iterator i = triangles.begin(); i != triangles.end(); i++) { if(i->vertex_indices[0] >= vertex_size || i->vertex_normal_indices[0] >= vertex_normals_size) throw runtime_error("Data integrity failure."); if(i->vertex_indices[1] >= vertex_size || i->vertex_normal_indices[1] >= vertex_normals_size) throw runtime_error("Data integrity failure."); if(i->vertex_indices[2] >= vertex_size || i->vertex_normal_indices[2] >= vertex_normals_size) throw runtime_error("Data integrity failure."); } } void GenerateVertexNormalsFromVertices() { vector<indexed_vertex> face_normals; face_normals.reserve(triangles.size()); face_normals.resize(triangles.size()); set<indexed_vertex>::const_iterator find_iter; size_t face_normal_index = 0; for(set<triangle>::iterator i = triangles.begin(); i != triangles.end(); i++) { indexed_vertex v[3]; // for each vertex in the triangle for(unsigned char j = 0; j < 3; j++) { // find the vertex with the matching index number indexed_vertex temp_vertex; temp_vertex.index = (*i).vertex_indices[j]; find_iter = vertices.find(temp_vertex); // if not found, look for bugs in the parsing/validation procedures called before this one :) if(find_iter == vertices.end()) throw runtime_error("Vertex index not found while generating vertex normals."); // else, extract position data v[j].x = (*find_iter).x; v[j].y = (*find_iter).y; v[j].z = (*find_iter).z; } // calculate vectors along two triangle edges double x1 = v[0].x - v[1].x; double y1 = v[0].y - v[1].y; double z1 = v[0].z - v[1].z; double x2 = v[1].x - v[2].x; double y2 = v[1].y - v[2].y; double z2 = v[1].z - v[2].z; // calculate face normal through cross product face_normals[face_normal_index].x = ( y1 * z2 ) - ( z1 * y2 ); face_normals[face_normal_index].y = ( z1 * x2 ) - ( x1 * z2 ); face_normals[face_normal_index].z = ( x1 * y2 ) - ( y1 * x2 ); double len = sqrt(face_normals[face_normal_index].x*face_normals[face_normal_index].x + face_normals[face_normal_index].y*face_normals[face_normal_index].y + face_normals[face_normal_index].z*face_normals[face_normal_index].z); if(len != 1.0) { face_normals[face_normal_index].x /= len; face_normals[face_normal_index].y /= len; face_normals[face_normal_index].z /= len; } face_normal_index++; } // clear vertex normals vertex_normals.clear(); // add the necessary number of vertex normals for(size_t i = 0; i < vertices.size(); i++) { indexed_vertex temp_vertex; temp_vertex.index = i; vertex_normals.insert(temp_vertex); } vector< vector< indexed_vertex > > temp_normals; temp_normals.resize(vertex_normals.size()); size_t normal_index = 0; for(set<triangle>::const_iterator i = triangles.begin(); i != triangles.end(); i++) { temp_normals[(*i).vertex_indices[0]].push_back(face_normals[normal_index]); temp_normals[(*i).vertex_indices[1]].push_back(face_normals[normal_index]); temp_normals[(*i).vertex_indices[2]].push_back(face_normals[normal_index]); normal_index++; } for(set<indexed_vertex>::iterator i = vertex_normals.begin(); i != vertex_normals.end(); i++) { double temp_x = 0.0f; double temp_y = 0.0f; double temp_z = 0.0f; // add up all face normals associated with this vertex for(size_t j = 0; j < temp_normals[(*i).index].size(); j++) { double local_temp_x = temp_normals[(*i).index][j].x; double local_temp_y = temp_normals[(*i).index][j].y; double local_temp_z = temp_normals[(*i).index][j].z; double local_len = sqrt(local_temp_x*local_temp_x + local_temp_y*local_temp_y + local_temp_z*local_temp_z); if(local_len != 1.0f) { local_temp_x /= local_len; local_temp_y /= local_len; local_temp_z /= local_len; } temp_x += local_temp_x; temp_y += local_temp_y; temp_z += local_temp_z; } // average them using a flat linear temp_x /= temp_normals[(*i).index].size(); temp_y /= temp_normals[(*i).index].size(); temp_z /= temp_normals[(*i).index].size(); // normalize the final result double len = sqrt(temp_x*temp_x + temp_y*temp_y + temp_z*temp_z); if(len != 1.0f) { temp_x /= len; temp_y /= len; temp_z /= len; } (*i).x = temp_x; (*i).y = temp_y; (*i).z = temp_z; } }};
objread.cpp:
#include "objread.h"int main(void){ obj_mesh obj; cout << "Reading from file..." << endl; try { obj.ReadFile("test.obj"); } catch(exception &e) { cout << "Error: " << e.what() << endl; return -1; } cout << "Read:" << endl; cout << "Vertices: " << obj.vertices.size() << endl; cout << "Vertex normals: " << obj.vertex_normals.size() << endl; cout << "Triangles: " <<obj.triangles.size() << endl; cout << endl << "Writing to file..." << endl; try { obj.WriteFile("test2.obj"); } catch(exception &e) { cout << "Error: " << e.what() << endl; return -1; } return 0;}
Wow.
Thanks I'll have a look thru this and see if I can figure out what you are doing.
Thanks heaps!!
-LW
-------------------LordsWarrior-------------------
So how does this deal with the size of faces?
like for an array faces model[maxFaces];
Is the "set" infinitely scalable as opposed to a predefined array?
-LW
like for an array faces model[maxFaces];
Is the "set" infinitely scalable as opposed to a predefined array?
-LW
-------------------LordsWarrior-------------------
Quote:
This is a finite amount of faces each model can have. Isnt this a problem?
If I make MAXFACES to small then the face count is restricted and may not be able to import some models. If MAXFACES is large (Million+), it is a waste of allocated memory when dealing with small models of 10s,100s,1000s faces etc.
Would it better to make faces a linked list and when displaying it just traverses the list? This makes it inifitely scalable...but then referencing each face may be a problem.
Don't use a linked list. Traversing the list could get slow based on the size of the model. Instead use dynamic arrays (ask for more info, I'm not sure what level programmer you are) or an STL container (as suggested, I'm not a big STL guy myself *ducks head*) for a list of (preferably unique, I'm not sure how OBJ files are stored) vertices (3 floats as you said, I recommend making a vector class) and a list of faces (3 integers corresponding to the list of vertices). As to get the size of the array you'll be storing the files in, I'm sure the .obj files keep the number of vertices and the number of faces.
You can use the smoothing groups to do "smooth" lighting by calculating vertex normals, however I'm sure wavefront stores the vertex normals for you. In that event, you probably won't need the smoothing groups for anything.
Best of luck with your project!
Without order nothing can exist - without chaos nothing can evolve.
The containers that store the mesh information are "infinitely scalable", yes. See the post directly above, re: STL -- this is what I use.
I'm sorry, but the mesh reader only reads the first three vertex indices of any face. If you have quads or n-gons in your mesh, tesselate them before exporting. Otherwise, you would have to alter the triangle class to store an "infinitely scalable" number of vertices/vertex normals, instead of just three of each type.
The ReadFile function can be used to open an .obj of nearly any size, limited only by the RAM available to you. And no, this is not achieved by pre-allocating an extremely large amount of RAM in hopes that it will be "enough". :)
The WriteFile function shows how to access the vertices/vertex normals/triangles after they have been read in.
As a side note:
Technically, for this example, using the set container might not have been the "best" way to store the data... however, it was what I needed to use for the job I was trying to finish.
So, if you feel adventurous, and want to understand the code more, alter the code to use a vector instead of a set to store the vertices/vertex normals/triangles.
I'm sorry, but the mesh reader only reads the first three vertex indices of any face. If you have quads or n-gons in your mesh, tesselate them before exporting. Otherwise, you would have to alter the triangle class to store an "infinitely scalable" number of vertices/vertex normals, instead of just three of each type.
The ReadFile function can be used to open an .obj of nearly any size, limited only by the RAM available to you. And no, this is not achieved by pre-allocating an extremely large amount of RAM in hopes that it will be "enough". :)
The WriteFile function shows how to access the vertices/vertex normals/triangles after they have been read in.
As a side note:
Technically, for this example, using the set container might not have been the "best" way to store the data... however, it was what I needed to use for the job I was trying to finish.
So, if you feel adventurous, and want to understand the code more, alter the code to use a vector instead of a set to store the vertices/vertex normals/triangles.
To store a model, use a vertex buffer object for the indices, and another vertex buffer object for the vertices. This is the most efficient way to transfer the data to the GPU; especially if you don't need to change the vertices and can give the buffers the STATIC_DRAW performance hint.
To efficiently draw only what's visible, there are various schemes, from a simple linked list with view frustum culling, to cell/portal based scene graphs with individual octrees within them. Google for "PVS" or "scene graph" for more information, or go look at something like Ogre3D or OpenSceneGraph.
To efficiently draw only what's visible, there are various schemes, from a simple linked list with view frustum culling, to cell/portal based scene graphs with individual octrees within them. Google for "PVS" or "scene graph" for more information, or go look at something like Ogre3D or OpenSceneGraph.
enum Bool { True, False, FileNotFound };
Thanks all
I think I may be over my head...you are talking a different language!
DO you have examples of the vertex buffer, indices buffer?
-LW
-------------------LordsWarrior-------------------
Before you move on to vertex arrays, what level of general and graphics programming are you capable of? You say you have openGL running and drawing, but what, exactly is it drawing? A modeler is quite a bit of work and if you're just starting out with 3D graphics, you're probably not ready.
So give us some examples of what you can do and we'll take it from there.
So give us some examples of what you can do and we'll take it from there.
Without order nothing can exist - without chaos nothing can evolve.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement
Recommended Tutorials
Advertisement