瀏覽代碼

Keep reference to multiple material when reading obj (#1280)

* Update issue_template.md

* Update issue_template.md

* Update issue_template.md

* Update issue_template.md

* Better doc

* WIP: Add function to load obj with material

* Fix error with gcc

* Read material and push it to the FM vector

* Code cleaning

* Use generic function overload

* switch to std::tuple<std::string, Index, Index >> for face material association

* Change SHA1 in libigl test data

* update the URL in the CMake to point to own fork

* Update LibiglDownloadExternal.cmake
DerouineauNicolas 6 年之前
父節點
當前提交
83b8e45103

+ 5 - 2
.github/issue_template.md

@@ -1,8 +1,11 @@
-[Describe your issue]
+#### Describe your issue
+
+...
 
 #### Check all that apply (change to `[x]`)
 - [ ] Windows
 - [ ] Mac OS X
 - [ ] Linux
+- [ ] I have tried the `dev` branch and the problem persists
 
-See <https://libigl.github.io/CONTRIBUTING/#bugreport> for more tips.
+See https://libigl.github.io/CONTRIBUTING/#bugreport for more tips.

+ 1 - 1
cmake/LibiglDownloadExternal.cmake

@@ -175,7 +175,7 @@ function(igl_download_test_data)
 	igl_download_project_aux(test_data
 		"${LIBIGL_EXTERNAL}/../tests/data"
 		GIT_REPOSITORY https://github.com/libigl/libigl-tests-data
-		GIT_TAG        adc66cabf712a0bd68ac182b4e7f8b5ba009c3dd
+		GIT_TAG        0689abc55bc12825e6c01ac77446f742839ff277
 	)
 endfunction()
 

+ 52 - 6
include/igl/readOBJ.cpp

@@ -35,7 +35,30 @@ IGL_INLINE bool igl::readOBJ(
             obj_file_name.c_str());
     return false;
   }
-  return igl::readOBJ(obj_file,V,TC,N,F,FTC,FN);
+  std::vector<std::tuple<std::string, Index, Index >> FM;
+  return igl::readOBJ(obj_file,V,TC,N,F,FTC,FN, FM);
+}
+
+template <typename Scalar, typename Index>
+IGL_INLINE bool igl::readOBJ(
+  const std::string obj_file_name,
+  std::vector<std::vector<Scalar > > & V,
+  std::vector<std::vector<Scalar > > & TC,
+  std::vector<std::vector<Scalar > > & N,
+  std::vector<std::vector<Index > > & F,
+  std::vector<std::vector<Index > > & FTC,
+  std::vector<std::vector<Index > > & FN,
+  std::vector<std::tuple<std::string, Index, Index >> &FM)
+{
+  // Open file, and check for error
+  FILE * obj_file = fopen(obj_file_name.c_str(),"r");
+  if(NULL==obj_file)
+  {
+    fprintf(stderr,"IOError: %s could not be opened...\n",
+            obj_file_name.c_str());
+    return false;
+  }
+  return igl::readOBJ(obj_file,V,TC,N,F,FTC,FN,FM);
 }
 
 template <typename Scalar, typename Index>
@@ -46,7 +69,8 @@ IGL_INLINE bool igl::readOBJ(
   std::vector<std::vector<Scalar > > & N,
   std::vector<std::vector<Index > > & F,
   std::vector<std::vector<Index > > & FTC,
-  std::vector<std::vector<Index > > & FN)
+  std::vector<std::vector<Index > > & FN,
+  std::vector<std::tuple<std::string, Index, Index >> &FM)
 {
   // File open was successful so clear outputs
   V.clear();
@@ -65,10 +89,16 @@ IGL_INLINE bool igl::readOBJ(
   std::string tic_tac_toe("#");
 #ifndef IGL_LINE_MAX
 #  define IGL_LINE_MAX 2048
+#endif
+
+#ifndef MATERIAL_LINE_MAX
+#  define MATERIAL_LINE_MAX 2048
 #endif
 
   char line[IGL_LINE_MAX];
-  int line_no = 1;
+  char currentmaterialref[MATERIAL_LINE_MAX] = "";
+  bool FMwasinit = false;
+  int line_no = 1, previous_face_no=0, current_face_no = 0;
   while (fgets(line, IGL_LINE_MAX, obj_file) != NULL)
   {
     char type[IGL_LINE_MAX];
@@ -193,6 +223,7 @@ IGL_INLINE bool igl::readOBJ(
           F.push_back(f);
           FTC.push_back(ftc);
           FN.push_back(fn);
+          current_face_no++;
         }else
         {
           fprintf(stderr,
@@ -200,10 +231,20 @@ IGL_INLINE bool igl::readOBJ(
           fclose(obj_file);
           return false;
         }
-      }else if(strlen(type) >= 1 && (type[0] == '#' ||
+      }else if(strlen(type) >= 1 && strcmp("usemtl",type)==0 )
+      {
+        if(FMwasinit){
+          FM.push_back(std::make_tuple(currentmaterialref,previous_face_no,current_face_no-1));
+          previous_face_no = current_face_no;
+        }
+        else{
+          FMwasinit=true;
+        }
+        sscanf(l, "%s\n", &currentmaterialref);
+      }
+      else if(strlen(type) >= 1 && (type[0] == '#' ||
             type[0] == 'g'  ||
             type[0] == 's'  ||
-            strcmp("usemtl",type)==0 ||
             strcmp("mtllib",type)==0))
       {
         //ignore comments or other shit
@@ -221,6 +262,8 @@ IGL_INLINE bool igl::readOBJ(
     }
     line_no++;
   }
+  if(strcmp(currentmaterialref,"")!=0)
+    FM.push_back(std::make_tuple(currentmaterialref,previous_face_no,current_face_no-1));
   fclose(obj_file);
 
   assert(F.size() == FN.size());
@@ -237,6 +280,8 @@ IGL_INLINE bool igl::readOBJ(
 {
   std::vector<std::vector<Scalar > > TC,N;
   std::vector<std::vector<Index > > FTC,FN;
+  std::vector<std::tuple<std::string, Index, Index >> FM;
+  
   return readOBJ(obj_file_name,V,TC,N,F,FTC,FN);
 }
 
@@ -351,6 +396,7 @@ IGL_INLINE bool igl::readOBJ(
   return true;
 }
 
+
 #ifdef IGL_STATIC_LIBRARY
 // Explicit template instantiation
 // generated by autoexplicit.sh
@@ -359,5 +405,5 @@ template bool igl::readOBJ<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matr
 template bool igl::readOBJ<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&);
 template bool igl::readOBJ<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&);
 template bool igl::readOBJ<double, int>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&);
-template bool igl::readOBJ<double, int>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&);
+template bool igl::readOBJ<double, int>(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<double, std::allocator<double> >, std::allocator<std::vector<double, std::allocator<double> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::vector<int, std::allocator<int> >, std::allocator<std::vector<int, std::allocator<int> > > >&, std::vector<std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, int>, std::allocator<std::tuple<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int, int> > >&);
 #endif

+ 32 - 1
include/igl/readOBJ.h

@@ -49,6 +49,36 @@ namespace igl
     std::vector<std::vector<Index > > & F,
     std::vector<std::vector<Index > > & FTC,
     std::vector<std::vector<Index > > & FN);
+  // Read a mesh from an ascii obj file, filling in vertex positions, normals
+  // and texture coordinates. Mesh may have faces of any number of degree
+  //
+  // Templates:
+  //   Scalar  type for positions and vectors (will be read as double and cast
+  //     to Scalar)
+  //   Index  type for indices (will be read as int and cast to Index)
+  // Inputs:
+  //  str  path to .obj file
+  // Outputs:
+  //   V  double matrix of vertex positions  #V by 3
+  //   TC  double matrix of texture coordinats #TC by 2
+  //   N  double matrix of corner normals #N by 3
+  //   F  #F list of face indices into vertex positions
+  //   FTC  #F list of face indices into vertex texture coordinates
+  //   FN  #F list of face indices into vertex normals
+  //   FM #tuple list containing (vertex index, normal index, texture coordinates index, material)
+  // Returns true on success, false on errors
+  template <typename Scalar, typename Index>
+  IGL_INLINE bool readOBJ(
+    const std::string obj_file_name, 
+    std::vector<std::vector<Scalar > > & V,
+    std::vector<std::vector<Scalar > > & TC,
+    std::vector<std::vector<Scalar > > & N,
+    std::vector<std::vector<Index > > & F,
+    std::vector<std::vector<Index > > & FTC,
+    std::vector<std::vector<Index > > & FN,
+    std::vector<std::tuple<std::string, Index, Index >> &FM
+    );
+
   // Inputs:
   //   obj_file  pointer to already opened .obj file 
   // Outputs:
@@ -61,7 +91,8 @@ namespace igl
     std::vector<std::vector<Scalar > > & N,
     std::vector<std::vector<Index > > & F,
     std::vector<std::vector<Index > > & FTC,
-    std::vector<std::vector<Index > > & FN);
+    std::vector<std::vector<Index > > & FN,
+    std::vector<std::tuple<std::string, Index, Index >> &FM);
   // Just V and F
   template <typename Scalar, typename Index>
   IGL_INLINE bool readOBJ(

+ 3 - 1
include/igl/read_triangle_mesh.cpp

@@ -105,6 +105,8 @@ IGL_INLINE bool igl::read_triangle_mesh(
   using namespace Eigen;
   vector<vector<double > > vV,vN,vTC,vC;
   vector<vector<int > > vF,vFTC,vFN;
+  vector<tuple<string, int, int>> FM;
+
   if(ext == "mesh")
   {
     // Convert extension to lower case
@@ -119,7 +121,7 @@ IGL_INLINE bool igl::read_triangle_mesh(
     }
   }else if(ext == "obj")
   {
-    if(!readOBJ(fp,vV,vTC,vN,vF,vFTC,vFN))
+    if(!readOBJ(fp,vV,vTC,vN,vF,vFTC,vFN,FM))
     {
       return false;
     }

+ 16 - 7
include/igl/uniformly_sample_two_manifold.h

@@ -11,19 +11,28 @@
 #include <Eigen/Dense>
 namespace igl
 {
-  // UNIFORMLY_SAMPLE_TWO_MANIFOLD Attempt to sample a mesh uniformly by
-  // furthest point relaxation as described in "Fast Automatic Skinning
-  // Transformations"
-  //
-  // [Jacobson et al. 12] Section 3.3.
+  // UNIFORMLY_SAMPLE_TWO_MANIFOLD Attempt to sample a mesh uniformly with
+  // k-points by furthest point relaxation as described in "Fast Automatic
+  // Skinning Transformations" [Jacobson et al. 12] Section 3.3. The input is
+  // not expected to be a typical 3D triangle mesh (e.g., [V,F]), instead each
+  // vertex is embedded in a high dimensional unit-hypercude ("weight space")
+  // defined by W, with triangles given by F. This algorithm will first conduct
+  // furthest point sampling from the set of vertices and then attempt to relax
+  // the sampled points along the surface of the high-dimensional triangle mesh
+  // (i.e., the output points may be in the middle of triangles, not just at
+  // vertices). An additional "push" factor will repel samples away from the
+  // corners of the hypercube.
   //
   // Inputs:
   //   W  #W by dim positions of mesh in weight space
   //   F  #F by 3 indices of triangles
-  //   k  number of samplse
+  //   k  number of samples
   //   push  factor by which corners should be pushed away
   // Outputs
-  //   WS  k by dim locations in weights space
+  //   WS  k by dim locations in weight space
+  //
+  // See also:
+  //   random_points_on_mesh
   //
   IGL_INLINE void uniformly_sample_two_manifold(
     const Eigen::MatrixXd & W,

+ 27 - 0
tests/include/igl/readOBJ.cpp

@@ -1,4 +1,7 @@
 #include <test_common.h>
+#include <iostream>
+#include <string>
+#include <tuple>
 
 TEST_CASE("readOBJ: simple", "[igl]")
 {
@@ -10,3 +13,27 @@ TEST_CASE("readOBJ: simple", "[igl]")
     REQUIRE (V.rows() == 8);
     REQUIRE (F.rows() == 12);
 }
+
+TEST_CASE("readOBJ: Obj with material", "[igl]")
+{
+    std::vector<std::vector<double > > V;
+    std::vector<std::vector<double > > TC;
+    std::vector<std::vector<double > > N;
+    std::vector<std::vector<int > > F;
+    std::vector<std::vector<int > > FTC;
+    std::vector<std::vector<int > >  FN;
+    std::vector<std::tuple<std::string, int, int>> FM;
+    test_common::load_obj_with_material<double, int>("cubewithmaterial.obj", V, TC, N, F, FTC, FN, FM);
+    REQUIRE (V.size() == 8);
+    REQUIRE (F.size() == 6);
+    for ( const auto& i : FM ) {
+        std::cout << "material ";
+        std::cout << std::get<0>(i) << ' ';
+        std::cout << "fstart ";
+        std::cout << std::get<1>(i) << ' ';
+        std::cout << "fend ";
+        std::cout << std::get<2>(i) << ' ';
+        std::cout << std::endl;
+    }
+    REQUIRE (FM.size() == 2);
+}

+ 19 - 0
tests/test_common.h

@@ -4,6 +4,7 @@
 #include <igl/read_triangle_mesh.h>
 #include <igl/find.h>
 #include <igl/readDMAT.h>
+#include <igl/readOBJ.h>
 
 #include <Eigen/Core>
 #include <catch2/catch.hpp>
@@ -86,6 +87,24 @@ namespace test_common
     igl::read_triangle_mesh(data_path(filename), V, F);
   }
 
+  template<typename DerivedV, typename DerivedF>
+  void load_obj_with_material(
+    const std::string& filename,
+    std::vector<std::vector<DerivedV >> & V,
+    std::vector<std::vector<DerivedV >> & TC,
+    std::vector<std::vector<DerivedV >> & N,
+    std::vector<std::vector<DerivedF >> & F,
+    std::vector<std::vector<DerivedF >> & FTC,
+    std::vector<std::vector<DerivedF >> & FN,
+    std::vector<std::tuple<std::string, DerivedF, DerivedF >> &FM)
+  {
+    igl::readOBJ(data_path(filename), V, TC, N, F, FTC, FN, FM);
+  }
+
+
+
+
+
   // TODO: this seems like a pointless indirection. Should just find and
   // replace test_common::load_matrix(X,...) with
   // igl::readDMAT(test_common::data_path(X),...)