// Copyright 2016 University of Minnesota // // TRIMESH Uses the BSD 2-Clause License (http://www.opensource.org/licenses/BSD-2-Clause) // Redistribution and use in source and binary forms, with or without modification, are // permitted provided that the following conditions are met: // 1. Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, this list // of conditions and the following disclaimer in the documentation and/or other materials // provided with the distribution. // THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF MINNESOTA, DULUTH OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef TRIMESH_HPP #define TRIMESH_HPP 1 #include #include #include #include #include #include // // Vector Class // Not complete, only has functions needed for this sample. // template class Vec { public: // Constructors Vec(){ data[0]=T(0); data[1]=T(0); data[2]=T(0); } Vec(T x_, T y_, T z_){ data[0]=x_; data[1]=y_; data[2]=z_; } // Data T data[3]; // Functions T operator[](int i) const { return data[i]; } T& operator[](int i){ return data[i]; } Vec &operator += (const Vec &x) { for(size_t i=0; idot(*this); } // squared length double len() const { return sqrt(len2()); } // length T dot(const Vec &v) const { T r(0); for(size_t i=0; i cross(const Vec<3,T> &v) const { assert(D == 3); // only defined for 3 dims return Vec<3,T>(data[1]*v[2] - data[2]*v[1], data[2]*v[0] - data[0]*v[2], data[0]*v[1] - data[1]*v[0]); } void normalize() { double l = len(); if( l<=0.0 ){ return; } for(size_t i=0; i static inline const Vec operator-(const Vec &v1, const Vec &v2){ Vec r; for(size_t i=0; i static inline const Vec operator*(const Vec &v, const T &x){ Vec r; for (size_t i=0; i Vec3f; typedef Vec<3,int> Vec3i; // // Triangle Mesh Class // class TriMesh { public: std::vector vertices; std::vector normals; std::vector colors; std::vector faces; // Compute normals if not loaded from obj // or if recompute is set to true. void need_normals( bool recompute=false ); // Sets a default vertex color if // they haven't been set. void need_colors( Vec3f default_color = Vec3f(0.4,0.4,0.4) ); // Loads an OBJ file bool load_obj( std::string file ); // Prints details about the mesh void print_details(); }; // // Implementation // void TriMesh::print_details(){ std::cout << "Vertices: " << vertices.size() << std::endl; std::cout << "Normals: " << normals.size() << std::endl; std::cout << "Colors: " << colors.size() << std::endl; std::cout << "Faces: " << faces.size() << std::endl; } void TriMesh::need_normals( bool recompute ){ if( vertices.size() == normals.size() && !recompute ){ return; } if( normals.size() != vertices.size() ){ normals.resize( vertices.size() ); } std::cout << "Computing TriMesh normals" << std::endl; const int nv = normals.size(); for( int i = 0; i < nv; ++i ){ normals[i][0] = 0.f; normals[i][1] = 0.f; normals[i][2] = 0.f; } int nf = faces.size(); for( int f = 0; f < nf; ++f ){ Vec3i face = faces[f]; const Vec3f &p0 = vertices[ face[0] ]; const Vec3f &p1 = vertices[ face[1] ]; const Vec3f &p2 = vertices[ face[2] ]; Vec3f a = p0-p1, b = p1-p2, c = p2-p0; float l2a = a.len2(), l2b = b.len2(), l2c = c.len2(); if (!l2a || !l2b || !l2c){ continue; } // check for zeros or nans Vec3f facenormal = a.cross( b ); normals[faces[f][0]] += facenormal * (1.0f / (l2a * l2c)); normals[faces[f][1]] += facenormal * (1.0f / (l2b * l2a)); normals[faces[f][2]] += facenormal * (1.0f / (l2c * l2b)); } for (int i = 0; i < nv; i++){ normals[i].normalize(); } } // end need normals void TriMesh::need_colors( Vec3f default_color ){ if( vertices.size() == colors.size() ){ return; } else{ colors.resize( vertices.size(), default_color ); } } // end need colors // Function to split a string into multiple strings, seperated by delimeter static void split_str( char delim, const std::string &str, std::vector *result ){ std::stringstream ss(str); std::string s; while( std::getline(ss, s, delim) ){ result->push_back(s); } } bool TriMesh::load_obj( std::string file ){ std::cout << "\nLoading " << file << std::endl; // README: // // The problem with standard obj files and opengl is that // there isn't a good way to make triangles with different indices // for vertices/normals. At least, not any way that I'm aware of. // So for now, we'll do the inefficient (but robust) way: // redundant vertices/normals. // std::vector temp_normals; std::vector temp_verts; std::vector temp_colors; // // First loop, make buffers // std::ifstream infile( file.c_str() ); if( infile.is_open() ){ std::string line; while( std::getline( infile, line ) ){ std::stringstream ss(line); std::string tok; ss >> tok; // Vertex if( tok == "v" ){ // First three location float x, y, z; ss >> x >> y >> z; temp_verts.push_back( Vec3f(x,y,z) ); // Next three colors float cx, cy, cz; if( ss >> cx >> cy >> cz ){ temp_colors.push_back( Vec3f(cx,cy,cz) ); } else { temp_colors.push_back( Vec3f(0.3f,0.3f,0.3f) ); } } // Normal if( tok == "vn" ){ float x, y, z; ss >> x >> y >> z; temp_normals.push_back( Vec3f(x,y,z) ); } } // end loop lines } // end load obj else { std::cerr << "\n**TriMesh::load_obj Error: Could not open file " << file << std::endl; return false; } // // Second loop, make faces // std::ifstream infile2( file.c_str() ); if( infile2.is_open() ){ std::string line; while( std::getline( infile2, line ) ){ std::stringstream ss(line); std::string tok; ss >> tok; // Face if( tok == "f" ){ Vec3i face; // Get the three vertices for( size_t i=0; i<3; ++i ){ std::string f_str; ss >> f_str; std::vector f_vals; split_str( '/', f_str, &f_vals ); assert(f_vals.size()>0); face[i] = vertices.size(); int v_idx = std::stoi(f_vals[0])-1; vertices.push_back( temp_verts[v_idx] ); colors.push_back( temp_colors[v_idx] ); // Check for normal if( f_vals.size()>2 ){ int n_idx = std::stoi(f_vals[2])-1; normals.push_back( temp_normals[n_idx] ); } } faces.push_back(face); // If it's a quad, make another triangle std::string last_vert=""; if( ss >> last_vert ){ Vec3i face2; face2[0] = face[0]; face2[1] = face[2]; std::vector f_vals; split_str( '/', last_vert, &f_vals ); assert(f_vals.size()>0); int v_idx = std::stoi(f_vals[0])-1; vertices.push_back( temp_verts[v_idx] ); colors.push_back( temp_colors[v_idx] ); face2[2] = vertices.size(); // Check for normal if( f_vals.size()>2 ){ int n_idx = std::stoi(f_vals[2])-1; normals.push_back( temp_normals[n_idx] ); } faces.push_back(face2); } } // end parse face } // end loop lines } // end load obj // Make sure we have normals if( !normals.size() ){ std::cout << "**Warning: normals not loaded so we'll compute them instead." << std::endl; need_normals(); } return true; } // end load obj #endif