#ifndef STIM_OBJ_H #define STIM_OBJ_H #include #include #include #include #include #include #include #include #include namespace stim{ /** This class provides an interface for loading, saving, and working with Wavefront OBJ files. * The class structure uses similar terminology to the OBJ file structure, so one familiar with it * should be able to work well with this class. * * This class currently provides the ability to load explicitly defined geometric objects, including point lists, * lines, and faces with positions, texture coordinates, and normals. This data is stored in a protected series * of lists that are accessible to classes that extend this one. * * Multiple helper classes are used to facilitate loading, storing, and saving geometry. These are protected * and therefore only designed to be used within the confines of the class. These helper classes include: * * vertex class - contains methods for loading, saving, and storing 1, 2, 3, and 4 component vectors * triplet class - contains methods for loading, saving, and storing indexed vertices used for geometric objects * geometry class - contains a list of triplets used to define a geometric structure, such as a face or line */ enum obj_type { OBJ_NONE, OBJ_LINE, OBJ_FACE, OBJ_POINTS, OBJ_TRIANGLE_STRIP }; template class obj{ protected: enum token_type { OBJ_INVALID, OBJ_V, OBJ_VT, OBJ_VN, OBJ_P, OBJ_L, OBJ_F }; struct vertex : public stim::vec{ using vec::push_back; using vec::size; using vec::at; using vec::resize; //basic constructors (call the stim::vector constructors) vertex(){} vertex(T x){ resize(1); at(0) = x; } vertex(T x, T y) : stim::vec(x, y){} vertex(T x, T y, T z) : stim::vec(x, y, z){} vertex(T x, T y, T z, T w) : stim::vec(x, y, z, w){} vertex(stim::vec rhs) : stim::vec(rhs){} //constructor creates a vertex from a line string vertex(std::string line){ //create a temporary vector used to store string tokens std::vector tokens = stim::parser::split(line, ' '); //create temporary storage for casting the token values T val; //for each token (skipping the first) for(unsigned int i = 1; i < tokens.size(); i++){ std::stringstream ss(tokens[i]); //create a stringstream object for casting ss>>val; //cast the token to the correct numerical value push_back(val); //push the coordinate into the vertex } } //output the vertex as a string std::string str(){ std::stringstream ss; for(int i = 0; i < size(); i++){ if(i > 0) ss<<' '; ss<{ //default constructor, empty triplet triplet(){} //create a triplet given a parameter list (OBJ indices start at 1, so 0 can be used to indicate no value) triplet(size_t v, size_t vt = 0, size_t vn = 0){ push_back(v); if(vn != 0){ push_back(vt); push_back(vn); } else if(vt != 0) push_back(vt); } //build a triplet from a string triplet(std::string s){ //create a temporary vector used to store string tokens std::vector tokens = stim::parser::split(s, '\\'); //std::cout<<"processing triplet: "<= 1) push_back( atoi( tokens[0].c_str() ) ); if(tokens.size() >= 2){ if(tokens[1].length()) push_back( atoi( tokens[1].c_str() ) ); else push_back(0); } if(tokens.size() == 3) push_back( atoi( tokens[2].c_str() ) ); } //output the geometry as a string std::string str(){ std::stringstream ss; ss<{ using std::vector::size; using std::vector::at; using std::vector::push_back; using std::vector::resize; geometry(){} //constructs a geometry object from a line read from an OBJ file geometry(std::string line){ //create a temporary vector used to store string tokens std::vector tokens = stim::parser::split(line, ' '); resize(tokens.size() - 1); //set the geometry size based on the number of tokens //for each triplet in the line for(unsigned int i = 1; i < tokens.size(); i++){ //construct the triplet and push it to the geometry //push_back( triplet(tokens[i]) ); at(i-1) = triplet(tokens[i]); } } //returns a vector containing the vertex indices for the geometry void get_v(std::vector& v){ v.resize(size()); //resize the vector to match the number of vertices for(unsigned int i = 0; i& vt){ vt.resize(size()); //resize the vector to match the number of vertices for(unsigned int i = 0; i& vn){ vn.resize(size()); //resize the vector to match the number of vertices for(unsigned int i = 0; i V; //vertex spatial position std::vector VN; std::vector VT; //structure lists std::vector L; //list of lines std::vector P; //list of points structures std::vector F; //list of faces //material lists std::vector< obj_material > M; //list of material descriptors std::vector Mf; //face index where each material begins //information for the current geometric object geometry current_geo; vertex current_vt; vertex current_vn; obj_material current_material; //stores the current material bool new_material; //flags if a material property has been changed since the last material was pushed //flags for the current geometric object obj_type current_type; bool geo_flag_vt; bool geo_flag_vn; bool vert_flag_vt; bool vert_flag_vn; void update_vt(vertex vt){ current_vt = vt; //the geometry vertex texture flag can only be set for the first vertex if(current_geo.size() == 0) geo_flag_vt = true; vert_flag_vt = true; } void update_vn(vertex vn){ current_vn = vn; //the geometry normal flag can only be set for the first vertex if(current_geo.size() == 0) geo_flag_vn = true; vert_flag_vn = true; } //create a triple and add it to the current geometry void update_v(vertex vv){ size_t v; size_t vt = 0; size_t vn = 0; //if the current geometry is using a texture coordinate, add the current texture coordinate to the geometry if(geo_flag_vt){ if(vert_flag_vt) //don't add it more than once VT.push_back(current_vt); vt = VT.size(); } //if the current geometry is using a normal, add the current texture coordinate to the geometry if(geo_flag_vn){ if(vert_flag_vn) //don't add it more than once VN.push_back(current_vn); vn = VN.size(); } //add the current vertex position to the geometry V.push_back(vv); v = V.size(); //create a triplet and add it to the current geometry current_geo.push_back(triplet(v, vt, vn)); //clear the vertex flags vert_flag_vt = false; vert_flag_vn = false; } void init(){ //clear all lists V.clear(); VT.clear(); VN.clear(); P.clear(); L.clear(); F.clear(); //initialize all of the flags current_type = OBJ_NONE; geo_flag_vt = false; geo_flag_vn = false; vert_flag_vt = false; vert_flag_vn = false; new_material = false; //initialize a new material to false (start with no material) } //gets the type of token representing the entry in the OBJ file token_type get_token(std::string s){ //if the line contains a vertex if(s[0] == 'v'){ if(s[1] == ' ') return OBJ_V; if(s[1] == 't') return OBJ_VT; if(s[1] == 'n') return OBJ_VN; } if(s[0] == 'l' && s[1] == ' ') return OBJ_L; if(s[0] == 'p' && s[1] == ' ') return OBJ_P; if(s[0] == 'f' && s[1] == ' ') return OBJ_F; return OBJ_INVALID; } public: /// Constructor loads a Wavefront OBJ file obj(std::string filename){ load(filename); } //functions for setting the texture coordinate for the next vertex void TexCoord(T x){ update_vt(vertex(x));} void TexCoord(T x, T y){ update_vt(vertex(x, y));} void TexCoord(T x, T y, T z){ update_vt(vertex(x, y, z));} void TexCoord(T x, T y, T z, T w){ update_vt(vertex(x, y, z, w));} //functions for setting the normal for the next vertex void Normal(T x){ update_vn(vertex(x));} void Normal(T x, T y){ update_vn(vertex(x, y));} void Normal(T x, T y, T z){ update_vn(vertex(x, y, z));} void Normal(T x, T y, T z, T w){ update_vn(vertex(x, y, z, w));} //functions for setting the next vertex position (note that this updates the current geometry) void Vertex(T x){ update_v(vertex(x));} void Vertex(T x, T y){ update_v(vertex(x, y));} void Vertex(T x, T y, T z){ update_v(vertex(x, y, z));} void Vertex(T x, T y, T z, T w){ update_v(vertex(x, y, z, w));} ///Material functions void matKa(T r, T g, T b) { new_material = true; current_material.ka[0] = r; current_material.ka[1] = g; current_material.ka[2] = b; } void matKa(std::string tex = std::string()) { new_material = true; current_material.tex_ka = tex; } void matKd(T r, T g, T b) { new_material = true; current_material.kd[0] = r; current_material.kd[1] = g; current_material.kd[2] = b; } void matKd(std::string tex = std::string()) { new_material = true; current_material.tex_kd = tex; } void matKs(T r, T g, T b) { new_material = true; current_material.ks[0] = r; current_material.ks[1] = g; current_material.ks[2] = b; } void matKs(std::string tex = std::string()) { new_material = true; current_material.tex_ks = tex; } void matNs(T n) { new_material = true; current_material.ns = n; } void matNs(std::string tex = std::string()) { new_material = true; current_material.tex_ns = tex; } void matIllum(int i) { new_material = true; current_material.illum = i; } void matD(std::string tex = std::string()) { new_material = true; current_material.tex_alpha = tex; } void matBump(std::string tex = std::string()) { new_material = true; current_material.tex_bump = tex; } void matDisp(std::string tex = std::string()) { new_material = true; current_material.tex_disp = tex; } void matDecal(std::string tex = std::string()) { new_material = true; current_material.tex_decal = tex; } ///This function starts drawing of a primitive object, such as a line, face, or point set /// @param t is the type of object to be drawn: OBJ_POINTS, OBJ_LINE, OBJ_FACE void Begin(obj_type t){ if (new_material) { //if a new material has been specified if (current_material.name == "") { //if a name wasn't given, create a new one std::stringstream ss; //create a name for it ss << "material" << M.size(); //base it on the material number current_material.name = ss.str(); } Mf.push_back(F.size()); //start the material at the current face index M.push_back(current_material); //push the current material current_material.name = ""; //reset the name of the current material new_material = false; } current_type = t; } //generates a list of faces from a list of points, assuming the input list forms a triangle strip std::vector genTriangleStrip(geometry s) { if (s.size() < 3) return std::vector(); //return an empty list if there aren't enough points to form a triangle size_t nt = s.size() - 2; //calculate the number of triangles in the strip std::vector r(nt); //create a list of geometry objects, where the number of faces = the number of triangles in the strip r[0].push_back(s[0]); r[0].push_back(s[1]); r[0].push_back(s[2]); for (size_t i = 1; i < nt; i++) { if (i % 2) { r[i].push_back(s[i + 1]); r[i].push_back(s[i + 0]); r[i].push_back(s[i + 2]); } else { r[i].push_back(s[i + 0]); r[i].push_back(s[i + 1]); r[i].push_back(s[i + 2]); } } return r; } /// This function terminates drawing of a primitive object, such as a line, face, or point set void End(){ //copy the current object to the appropriate list if(current_geo.size() != 0) { switch(current_type){ case OBJ_NONE: std::cout<<"STIM::OBJ error, objEnd() called before objBegin()."< tstrip = genTriangleStrip(current_geo); //generate a list of faces from the current geometry F.insert(F.end(), tstrip.begin(), tstrip.end()); //insert all of the triangles into the face list break; } } //clear everything current_type = OBJ_NONE; current_geo.clear(); vert_flag_vt = false; vert_flag_vn = false; geo_flag_vt = false; geo_flag_vn = false; } //temporary convenience method void rev(){ if(current_geo.size() != 0) // current_geo.reverse(current_geo.begin(), current_geo.end()); std::reverse(current_geo.begin(), current_geo.end()); } //output the OBJ structure as a string std::string str(){ std::stringstream ss; unsigned int i; //output all of the vertices if(V.size()){ ss<<"#vertex positions"< getV(unsigned int vi){ stim::vec v = V[vi]; return v; } std::vector< stim::vec > getAllV() { std::vector< stim::vec > v(V.size()); for (unsigned i = 0; i < V.size(); i++) v[i] = V[i]; return v; } std::vector< stim::vec > getAllV(std::vector vertexIndices){ std::vector< stim::vec > allVerticesList; for (unsigned int i=0; i >allVerticesList, stim::vec v0) { int result = 0; for (unsigned int i = 0; i < allVerticesList.size() ; i++) { if (allVerticesList[i] == v0) { result = i; } } return result; } /// Retrieve the vertex texture coordinate at index i /// @param vti is the desired index stim::vec getVT(unsigned int vti){ stim::vec vt = VT[vti]; return vt; } /// Retrieve the vertex normal at index i /// @param vni is the desired index stim::vec getVN(unsigned int vni){ stim::vec vn = VN[vni]; return vn; } /// Retrieve a vertex stored at a list of given indices /// @param vi is an array containing a series of indices std::vector< stim::vec > getV( std::vector vi ){ std::vector< stim::vec > v; v.resize(vi.size()); //pre-allocate an array of vertices for(unsigned i = 0; i < vi.size(); i++) v[i] = V[vi[i] - 1]; return v; //return the array of vertices } /// Retrieve a vertex stored at a list of given indices /// @param vi is an array containing a series of indices std::vector< stim::vec > getVT( std::vector vti ){ std::vector< stim::vec > vt; vt.resize(vti.size()); //pre-allocate an array of vertices for(unsigned i = 0; i < vti.size(); i++) vt[i] = VT[vti[i] - 1]; return vt; //return the array of vertices } /// Retrieve a vertex stored at a list of given indices /// @param vi is an array containing a series of indices std::vector< stim::vec > getVN( std::vector vni ){ std::vector< stim::vec > vn; vn.resize(vni.size()); //pre-allocate an array of vertices for(unsigned i = 0; i < vni.size(); i++) vn[i] = VN[vni[i] - 1]; return vn; //return the array of vertices } stim::vec centroid(){ //get the number of coordinates size_t N = V[0].size(); //allocate space for the minimum and maximum coordinate points (bounding box corners) stim::vec vmin, vmax; vmin.resize(N); vmax.resize(N); //find the minimum and maximum value for each coordinate size_t NV = V.size(); for(size_t v = 0; v < NV; v++) for(size_t i = 0; i < N; i++){ if(V[v][i] < vmin[i]) vmin[i] = V[v][i]; if(V[v][i] > vmax[i]) vmax[i] = V[v][i]; } //find the centroid using the min and max points stim::vec c = vmin * 0.5 + vmax * 0.5; return c; } stim::vec dimensions() { //get the number of coordinates size_t N = V[0].size(); //allocate space for the minimum and maximum coordinate points (bounding box corners) stim::vec vmin, vmax; vmin.resize(N); vmax.resize(N); //find the minimum and maximum value for each coordinate size_t NV = V.size(); for (size_t v = 0; v < NV; v++) for (size_t i = 0; i < N; i++) { if (V[v][i] < vmin[i]) vmin[i] = V[v][i]; if (V[v][i] > vmax[i]) vmax[i] = V[v][i]; } //find the soze using the min and max points stim::vec d = vmax - vmin; return d; } /// Returns the number of lines in the OBJ structure unsigned int numL(){ return (unsigned)L.size(); } /// Returns all points in the line corresponding to a given index /// @param i is the index of the desired line std::vector< stim::vec > getL_V(unsigned int i){ //get the number of points in the specified line unsigned int nP = L[i].size(); //create a vector of points std::vector< stim::vec > l; //set the size of the vector l.resize(nP); //copy the points from the point list to the stim vector unsigned int pie; for(unsigned int p = 0; p < nP; p++){ //get the index of the geometry point pie = L[i][p][0] - 1; //get the coordinates of the current point stim::vec newP = V[pie]; //copy the point into the vector l[p] = newP; } return l; } /// Returns the vertex indices for the specified line /// @param i is the index of the line std::vector< unsigned int > getL_Vi(unsigned int i){ unsigned int nP = L[i].size(); std::vector< unsigned int > l; //set the size of the vector l.resize(nP); //copy the indices from the geometry structure to the array for(unsigned int p = 0; p < nP; p++){ l[p] = L[i][p][0]; } return l; } /// Returns a vector containing the list of texture coordinates associated with each point of a line. /// @param i is the index of the desired line std::vector< stim::vec > getL_VT(unsigned int i){ //get the number of points in the specified line unsigned int nP = L[i].size(); //create a vector of points std::vector< stim::vec > l; //set the size of the vector l.resize(nP); //copy the points from the point list to the stim vector unsigned int pie; for(unsigned int p = 0; p < nP; p++){ //get the index of the geometry point pie = L[i][p][1] - 1; //get the coordinates of the current point stim::vec newP = VT[pie]; //copy the point into the vector l[p] = newP; } return l; } /// Add an array of vertices to the vertex list unsigned int addV(std::vector< stim::vec > vertices){ unsigned int NV = vertices.size(); //get the total number of new vertices unsigned int currentV = V.size() + 1; //store the index to the first submitted point //for each vertex for(unsigned int vi = 0; vi < NV; vi++){ vertex v(vertices[vi]); V.push_back(v); } //return the index to the first point return currentV; } /// Add a line to the object void addLine(std::vector v, std::vector vt = std::vector(), std::vector vn = std::vector()){ //create a new line geometry geometry new_line; unsigned int v_i, vt_i, vn_i; for(unsigned int i = 0; i < v.size(); i++){ v_i = vt_i = vn_i = 0; v_i = v[i]; if(vt.size() != 0) vt_i = vt[i]; if(vn.size() != 0) vn_i = vn[i]; new_line.push_back(triplet(v_i, vt_i, vn_i)); } //push the new geometry to the line list L.push_back(new_line); } /// Fills three std::vector structures with the indices representing a line /// @param l is the line index /// @param vi is a vertex index array that will be filled void getLinei(unsigned l, std::vector& vi){ L[l-1].get_v(vi); } /// Fills three std::vector structures with the indices representing a line /// @param l is the line index /// @param vi is a vertex index array that will be filled /// @param vti is a vertex texture coordinate index array that will be filled void getLinei(unsigned l, std::vector& vi, std::vector& vti){ getLinei(l, vi); L[l-1].get_vt(vti); } /// Fills three std::vector structures with the indices representing a line /// @param l is the line index /// @param vi is a vertex index array that will be filled /// @param vti is a vertex texture coordinate index array that will be filled /// @param vni is a vertex normal index array that will be filled void getLinei(unsigned l, std::vector& vi, std::vector& vti, std::vector& vni){ getLinei(l, vi, vti); L[l-1].get_vn(vni); } /// Returns a list of points corresponding to the vertices of a line void getLine(unsigned l, std::vector< stim::vec >& v){ std::vector vi; //create a vector to store indices to vertices getLinei(l, vi); //get the indices for the line vertices v = getV(vi); //get the vertices corresponding to the indices } void getLine(unsigned l, std::vector< stim::vec >& v, std::vector< stim::vec >& vt){ std::vector vi, vti; getLinei(l, vi, vti); //get the indices for the line vertices v = getV(vi); //get the vertices corresponding to the indices vt = getVT(vti); } void getLine(unsigned l, std::vector< stim::vec >& v, std::vector< stim::vec >& vt, std::vector< stim::vec >& vn){ std::vector vi, vti, vni; getLinei(l, vi, vti, vni); //get the indices for the line vertices v = getV(vi); //get the vertices corresponding to the indices vt = getVT(vti); vn = getVN(vni); } /// Return the number of vertices in a line /// @param l is the line index unsigned getLineN(unsigned l) { return (unsigned)L[l].size(); } }; }; #endif