Browse Source

faster file formats

Former-commit-id: 19ec65cf853477f2865eba48d4ce4cf34fd3d416
Alec Jacobson (jalec 12 years ago
parent
commit
974039f95c

+ 1 - 1
VERSION.txt

@@ -3,4 +3,4 @@
 # Anyone may increment Minor to indicate a small change.
 # Major indicates a large change or large number of changes (upload to website)
 # World indicates a substantial change or release
-0.1.5
+0.1.6

+ 44 - 0
file-formats/index.html

@@ -39,6 +39,50 @@
     <li><a href=http://en.wikipedia.org/wiki/Truevision_TGA>.tga</a> Truevision TGA or TARGA image file format. IGLLIB supports only very basic reading and writing RGB/RGBA files without colormaps and (unverified) run-length compression.</li>
     <li><a href="./tgf.html">.tgf</a> ASCII files for representing control handle graphs</li>
   </ul>
+  <h3>Triangle mesh file format performance</h3>
+  <p>
+  <a
+  href="http://en.wikipedia.org/wiki/Wavefront_.obj_file#File_format">.obj</a>
+  and <a href="http://tetgen.berlios.de/fformats.off.html">.off</a> file
+  formats support meshes with arbitrary polygon degrees. However, often we are
+  only working with triangle meshes. Further, .obj files do not have useful
+  headers revealing the number of elements. For triangle meshes, .off and .obj
+  are inferior file formats to the <a
+  href="http://www.ann.jussieu.fr/frey/publications/RT-0253.pdf#page=33">.mesh</a>
+  file format. The current (version 0.1.6) IO functions for these file formats perform as follows for reading and writing a 300,000 triangle mesh:
+  </p>
+  <pre><code>
+writeOBJ:  1.33742 secs
+writeOFF:  0.510111 secs
+writeMESH: 0.218139 secs
+
+readOBJ:   1.3782 secs
+readOFF:   0.691496 secs
+readMESH:  0.242315 secs
+  </code></pre>
+  <p>
+  This reveals that .mesh is 6.5x faster than .obj and about 2.5x faster than .off.
+  </p>
+  <p>
+  While .obj files support normals, it is typically much faster to (re)compute
+  normals from the geometry using <code>per_face_normals</code>,
+  <code>per_vertex_normals</code>, <code>per_corner_normals</code> than to read
+  and write them to files.</p>
+  <p>It gets even better if you're willing to use a nonstandard format. If your
+  triangle mesh is in (<code>V</code>,<code>F</code>) then you can read and
+  write those variables as dense matrices of doubles to 
+  <a href="./dmat.html">.dmat</a> uncompressed <strong>binary</strong> files.
+  This not only ensures perfect precision but also big speed ups. On that same
+  300,000 triangle mesh, .dmat achieves:</p>
+  <pre><code>
+writeDMAT: 0.0384338 secs
+
+readDMAT:  0.0117921 secs
+  </code></pre>
+  <p>This reveals that binary .dmat files are 34x/116x faster at writing and
+  reading than .obj and a hefty 5x/20x over .mesh. In this case it may pay to
+  compute normals once into <code>N</code> and also read and write it to a
+  .dmat file.</p>
   </div>
   </body>
 </html>

+ 22 - 1
include/igl/readDMAT.cpp

@@ -85,6 +85,26 @@ IGL_INLINE bool igl::readDMAT(const std::string file_name,
     }
   }
 
+  // Try to read header for binary part
+  head_success = readDMAT_read_header(fp,num_rows,num_cols);
+  if(head_success == 0)
+  {
+    assert(W.size() == 0);
+    // Resize for output
+    W.resize(num_rows,num_cols);
+    double * Wraw = new double[num_rows*num_cols];
+    fread(Wraw, sizeof(double), num_cols*num_rows, fp);
+    // Loop over columns slowly
+    for(int j = 0;j < num_cols;j++)
+    {
+      // loop over rows (down columns) quickly
+      for(int i = 0;i < num_rows;i++)
+      {
+        W(i,j) = Wraw[j*num_rows+i];
+      }
+    }
+  }
+
   fclose(fp);
   return true;
 }
@@ -145,7 +165,7 @@ IGL_INLINE bool igl::readDMAT(
     // Resize for output
     W.resize(num_rows,typename std::vector<Scalar>(num_cols));
     double * Wraw = new double[num_rows*num_cols];
-    fread(Wraw, 8, num_cols*num_rows, fp);
+    fread(Wraw, sizeof(double), num_cols*num_rows, fp);
     // Loop over columns slowly
     for(int j = 0;j < num_cols;j++)
     {
@@ -165,4 +185,5 @@ IGL_INLINE bool igl::readDMAT(
 // Explicit template specialization
 template bool igl::readDMAT<Eigen::Matrix<double, -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> >&);
 template bool igl::readDMAT<double>(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> > > >&);
+template bool igl::readDMAT<Eigen::Matrix<int, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&);
 #endif

+ 217 - 16
include/igl/readMESH.cpp

@@ -225,36 +225,237 @@ IGL_INLINE bool igl::readMESH(
   Eigen::PlainObjectBase<DerivedT>& T,
   Eigen::PlainObjectBase<DerivedF>& F)
 {
-  std::vector<std::vector<double> > vV,vT,vF;
-  bool success = igl::readMESH(mesh_file_name,vV,vT,vF);
-  if(!success)
+  using namespace std;
+  using namespace igl;
+  FILE * mesh_file = fopen(mesh_file_name.c_str(),"r");
+  if(NULL==mesh_file)
+  {
+    fprintf(stderr,"IOError: %s could not be opened...",mesh_file_name.c_str());
+    return false;
+  }
+#ifndef LINE_MAX
+#  define LINE_MAX 2048
+#endif
+  char line[LINE_MAX];
+  bool still_comments;
+
+  // eat comments at beginning of file
+  still_comments= true;
+  while(still_comments)
+  {
+    fgets(line,LINE_MAX,mesh_file);
+    still_comments = (line[0] == '#' || line[0] == '\n');
+  }
+
+  char str[LINE_MAX];
+  sscanf(line," %s",str);
+  // check that first word is MeshVersionFormatted
+  if(0!=strcmp(str,"MeshVersionFormatted"))
+  {
+    fprintf(stderr,
+      "Error: first word should be MeshVersionFormatted not %s\n",str);
+    fclose(mesh_file);
+    return false;
+  }
+  int one = -1;
+  if(2 != sscanf(line,"%s %d",str,&one))
+  {
+    // 1 appears on next line?
+    fscanf(mesh_file," %d",&one);
+  }
+  if(one != 1)
+  {
+    fprintf(stderr,"Error: second word should be 1 not %d\n",one);
+    fclose(mesh_file);
+    return false;
+  }
+
+  // eat comments
+  still_comments= true;
+  while(still_comments)
+  {
+    fgets(line,LINE_MAX,mesh_file);
+    still_comments = (line[0] == '#' || line[0] == '\n');
+  }
+
+  sscanf(line," %s",str);
+  // check that third word is Dimension
+  if(0!=strcmp(str,"Dimension"))
   {
-    // readMESH already printed error message to std err
+    fprintf(stderr,"Error: third word should be Dimension not %s\n",str);
+    fclose(mesh_file);
     return false;
   }
-  bool V_rect = igl::list_to_matrix(vV,V);
-  if(!V_rect)
+  int three = -1;
+  if(2 != sscanf(line,"%s %d",str,&three))
   {
-    // igl::list_to_matrix(vV,V) already printed error message to std err
+    // 1 appears on next line?
+    fscanf(mesh_file," %d",&three);
+  }
+  if(three != 3)
+  {
+    fprintf(stderr,"Error: only Dimension 3 supported not %d\n",three);
+    fclose(mesh_file);
     return false;
   }
-  bool T_rect = igl::list_to_matrix(vT,T);
-  if(!T_rect)
+
+  // eat comments
+  still_comments= true;
+  while(still_comments)
+  {
+    fgets(line,LINE_MAX,mesh_file);
+    still_comments = (line[0] == '#' || line[0] == '\n');
+  }
+
+  sscanf(line," %s",str);
+  // check that fifth word is Vertices
+  if(0!=strcmp(str,"Vertices"))
   {
-    // igl::list_to_matrix(vT,T) already printed error message to std err
+    fprintf(stderr,"Error: fifth word should be Vertices not %s\n",str);
+    fclose(mesh_file);
     return false;
   }
-  bool F_rect = igl::list_to_matrix(vF,F);
-  if(!F_rect)
+
+  //fgets(line,LINE_MAX,mesh_file);
+
+  int number_of_vertices;
+  if(1 != fscanf(mesh_file," %d",&number_of_vertices) || number_of_vertices > 1000000000)
   {
-    // igl::list_to_matrix(vF,F) already printed error message to std err
+    fprintf(stderr,"Error: expecting number of vertices less than 10^9...\n");
+    fclose(mesh_file);
     return false;
   }
-  assert(V.cols() == 3);
-  assert(T.cols() == 4);
-  assert(F.cols() == 3);
+  // allocate space for vertices
+  V.resize(number_of_vertices,3);
+  int extra;
+  for(int i = 0;i<number_of_vertices;i++)
+  {
+    double x,y,z;
+    if(4 != fscanf(mesh_file," %lg %lg %lg %d",&x,&y,&z,&extra))
+    {
+      fprintf(stderr,"Error: expecting vertex position...\n");
+      fclose(mesh_file);
+      return false;
+    }
+    V(i,0) = x;
+    V(i,1) = y;
+    V(i,2) = z;
+  }
+
+  // eat comments
+  still_comments= true;
+  while(still_comments)
+  {
+    fgets(line,LINE_MAX,mesh_file);
+    still_comments = (line[0] == '#' || line[0] == '\n');
+  }
+
+  sscanf(line," %s",str);
+  // check that sixth word is Triangles
+  if(0!=strcmp(str,"Triangles"))
+  {
+    fprintf(stderr,"Error: sixth word should be Triangles not %s\n",str);
+    fclose(mesh_file);
+    return false;
+  }
+  int number_of_triangles;
+  if(1 != fscanf(mesh_file," %d",&number_of_triangles))
+  {
+    fprintf(stderr,"Error: expecting number of triangles...\n");
+    fclose(mesh_file);
+    return false;
+  }
+  // allocate space for triangles
+  F.resize(number_of_triangles,3);
+  // triangle indices
+  int tri[3];
+  for(int i = 0;i<number_of_triangles;i++)
+  {
+    if(4 != fscanf(mesh_file," %d %d %d %d",&tri[0],&tri[1],&tri[2],&extra))
+    {
+      printf("Error: expecting triangle indices...\n");
+      return false;
+    }
+    for(int j = 0;j<3;j++)
+    {
+      F(i,j) = tri[j]-1;
+    }
+  }
+
+  // eat comments
+  still_comments= true;
+  while(still_comments)
+  {
+    fgets(line,LINE_MAX,mesh_file);
+    still_comments = (line[0] == '#' || line[0] == '\n');
+  }
+
+  sscanf(line," %s",str);
+  // check that sixth word is Triangles
+  if(0!=strcmp(str,"Tetrahedra"))
+  {
+    fprintf(stderr,"Error: seventh word should be Tetrahedra not %s\n",str);
+    fclose(mesh_file);
+    return false;
+  }
+  int number_of_tetrahedra;
+  if(1 != fscanf(mesh_file," %d",&number_of_tetrahedra))
+  {
+    fprintf(stderr,"Error: expecting number of tetrahedra...\n");
+    fclose(mesh_file);
+    return false;
+  }
+  // allocate space for tetrahedra
+  T.resize(number_of_tetrahedra,4);
+  // tet indices
+  int a,b,c,d;
+  for(int i = 0;i<number_of_tetrahedra;i++)
+  {
+    if(5 != fscanf(mesh_file," %d %d %d %d %d",&a,&b,&c,&d,&extra))
+    {
+      fprintf(stderr,"Error: expecting tetrahedra indices...\n");
+      fclose(mesh_file);
+      return false;
+    }
+    T(i,0) = a-1;
+    T(i,1) = b-1;
+    T(i,2) = c-1;
+    T(i,3) = d-1;
+  }
+  fclose(mesh_file);
   return true;
 }
+//{
+//  std::vector<std::vector<double> > vV,vT,vF;
+//  bool success = igl::readMESH(mesh_file_name,vV,vT,vF);
+//  if(!success)
+//  {
+//    // readMESH already printed error message to std err
+//    return false;
+//  }
+//  bool V_rect = igl::list_to_matrix(vV,V);
+//  if(!V_rect)
+//  {
+//    // igl::list_to_matrix(vV,V) already printed error message to std err
+//    return false;
+//  }
+//  bool T_rect = igl::list_to_matrix(vT,T);
+//  if(!T_rect)
+//  {
+//    // igl::list_to_matrix(vT,T) already printed error message to std err
+//    return false;
+//  }
+//  bool F_rect = igl::list_to_matrix(vF,F);
+//  if(!F_rect)
+//  {
+//    // igl::list_to_matrix(vF,F) already printed error message to std err
+//    return false;
+//  }
+//  assert(V.cols() == 3);
+//  assert(T.cols() == 4);
+//  assert(F.cols() == 3);
+//  return true;
+//}
 
 #ifndef IGL_HEADER_ONLY
 // Explicit template specialization

+ 2 - 0
include/igl/readMESH.h

@@ -20,6 +20,8 @@ namespace igl
   //   V  double matrix of vertex positions  #V by 3
   //   T  #T list of tet indices into vertex positions
   //   F  #F list of face indices into vertex positions
+  //
+  // Known bugs: Holes and regions are not supported
   template <typename Scalar, typename Index>
   IGL_INLINE bool readMESH(
     const std::string mesh_file_name,

+ 7 - 7
include/igl/readOBJ.cpp

@@ -278,13 +278,13 @@ IGL_INLINE bool igl::readOBJ(
 
 template <typename DerivedV, typename DerivedF, typename DerivedT, typename Index>
 IGL_INLINE bool igl::readOBJPoly(
-                             const std::string str,
-                             Eigen::PlainObjectBase<DerivedV>& V,
-                             std::vector<std::vector< Index > >& F,
-                             Eigen::PlainObjectBase<DerivedV>& CN,
-                             Eigen::PlainObjectBase<DerivedF>& FN,
-                             Eigen::PlainObjectBase<DerivedT>& TC,
-                             Eigen::PlainObjectBase<DerivedF>& FTC)
+  const std::string str,
+  Eigen::PlainObjectBase<DerivedV>& V,
+  std::vector<std::vector< Index > >& F,
+  Eigen::PlainObjectBase<DerivedV>& CN,
+  Eigen::PlainObjectBase<DerivedF>& FN,
+  Eigen::PlainObjectBase<DerivedT>& TC,
+  Eigen::PlainObjectBase<DerivedF>& FTC)
 {
   std::vector<std::vector<double> > vV,vTC,vN;
   std::vector<std::vector<Index> > vF,vFTC,vFN;

+ 36 - 12
include/igl/writeDMAT.cpp

@@ -2,11 +2,14 @@
 
 #include <cstdio>
 #ifndef IGL_NO_EIGEN
-#  include <Eigen/Dense>
+#  include <Eigen/Core>
 #endif
 
 template <class Mat>
-IGL_INLINE bool igl::writeDMAT(const std::string file_name, const Mat & W)
+IGL_INLINE bool igl::writeDMAT(
+  const std::string file_name, 
+  const Mat & W,
+  const bool ascii)
 {
   FILE * fp = fopen(file_name.c_str(),"w");
   if(fp == NULL)
@@ -14,16 +17,37 @@ IGL_INLINE bool igl::writeDMAT(const std::string file_name, const Mat & W)
     fprintf(stderr,"IOError: writeDMAT() could not open %s...",file_name.c_str());
     return false; 
   }
-  // first line contains number of rows and number of columns
-  fprintf(fp,"%d %d\n",(int)W.cols(),(int)W.rows());
-  // Loop over columns slowly
-  for(int j = 0;j < W.cols();j++)
+  if(ascii)
   {
-    // loop over rows (down columns) quickly
-    for(int i = 0;i < W.rows();i++)
+    // first line contains number of rows and number of columns
+    fprintf(fp,"%d %d\n",(int)W.cols(),(int)W.rows());
+    // Loop over columns slowly
+    for(int j = 0;j < W.cols();j++)
     {
-      fprintf(fp,"%0.17lg\n",(double)W(i,j));
+      // loop over rows (down columns) quickly
+      for(int i = 0;i < W.rows();i++)
+      {
+        fprintf(fp,"%0.17lg\n",(double)W(i,j));
+      }
     }
+  }else
+  {
+    // write header for ascii
+    fprintf(fp,"0 0\n");
+    // first line contains number of rows and number of columns
+    fprintf(fp,"%d %d\n",(int)W.cols(),(int)W.rows());
+    Eigen::MatrixXd Wd = W.template cast<double>();
+    //// Loop over columns slowly
+    //for(int j = 0;j < W.cols();j++)
+    //{
+    //  // loop over rows (down columns) quickly
+    //  for(int i = 0;i < W.rows();i++)
+    //  {
+    //    double d = (double)W(i,j);
+    //    fwrite(&d,sizeof(double),1,fp);
+    //  }
+    //}
+    fwrite(Wd.data(),sizeof(double),Wd.size(),fp);
   }
   fclose(fp);
   return true;
@@ -66,8 +90,8 @@ IGL_INLINE bool igl::writeDMAT(
 #ifndef IGL_HEADER_ONLY
 // Explicit template specialization
 // generated by autoexplicit.sh
-template bool igl::writeDMAT<Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::Matrix<double, -1, -1, 0, -1, -1> const&);
+template bool igl::writeDMAT<Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::Matrix<double, -1, -1, 0, -1, -1> const&,bool);
 template bool igl::writeDMAT<double>(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> > > >);
-template bool igl::writeDMAT<Eigen::Matrix<int, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::Matrix<int, -1, -1, 0, -1, -1> const&);
-template bool igl::writeDMAT<Eigen::Matrix<double, -1, 1, 0, -1, 1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::Matrix<double, -1, 1, 0, -1, 1> const&);
+template bool igl::writeDMAT<Eigen::Matrix<int, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::Matrix<int, -1, -1, 0, -1, -1> const&, bool);
+template bool igl::writeDMAT<Eigen::Matrix<double, -1, 1, 0, -1, 1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >, Eigen::Matrix<double, -1, 1, 0, -1, 1> const&, bool);
 #endif

+ 5 - 1
include/igl/writeDMAT.h

@@ -13,10 +13,14 @@ namespace igl
   // Inputs:
   //   file_name  path to .dmat file
   //   W  eigen matrix containing to-be-written coefficients
+  //   ascii  write ascii file {true}
   // Returns true on success, false on error
   //
   template <class Mat>
-  IGL_INLINE bool writeDMAT(const std::string file_name, const Mat & W);
+  IGL_INLINE bool writeDMAT(
+    const std::string file_name, 
+    const Mat & W,
+    const bool ascii=true);
   template <typename Scalar>
   IGL_INLINE bool writeDMAT(
     const std::string file_name, 

+ 31 - 5
include/igl/writeMESH.cpp

@@ -1,10 +1,13 @@
 #include "writeMESH.h"
 
-#include <cstdio>
 #include "verbose.h"
 #include "matrix_to_list.h"
 #include <Eigen/Core>
 
+#include <iostream>
+#include <fstream>
+#include <cstdio>
+
 template <typename Scalar, typename Index>
 IGL_INLINE bool igl::writeMESH(
   const std::string mesh_file_name,
@@ -37,14 +40,36 @@ IGL_INLINE bool igl::writeMESH(
 template <typename DerivedV, typename DerivedT, typename DerivedF>
 IGL_INLINE bool igl::writeMESH(
   const std::string str,
-  const Eigen::MatrixBase<DerivedV> & V, 
-  const Eigen::MatrixBase<DerivedT> & T,
-  const Eigen::MatrixBase<DerivedF> & F)
+  const Eigen::PlainObjectBase<DerivedV> & V, 
+  const Eigen::PlainObjectBase<DerivedT> & T,
+  const Eigen::PlainObjectBase<DerivedF> & F)
 {
   using namespace std;
   using namespace igl;
   using namespace Eigen;
 
+  //// This is (surprisingly) slower than the C-ish code below
+  //ofstream mesh_file;
+  //mesh_file.open(str.c_str());
+  //if(!mesh_file.is_open())
+  //{
+  //  cerr<<"IOError: "<<str<<" could not be opened..."<<endl;
+  //  return false;
+  //}
+  //IOFormat format(FullPrecision,DontAlignCols," ","\n",""," 1","","");
+  //mesh_file<<"MeshVersionFormatted 1\n";
+  //mesh_file<<"Dimension 3\n";
+  //mesh_file<<"Vertices\n";
+  //mesh_file<<V.rows()<<"\n";
+  //mesh_file<<V.format(format)<<"\n";
+  //mesh_file<<"Triangles\n";
+  //mesh_file<<F.rows()<<"\n";
+  //mesh_file<<(F.array()+1).eval().format(format)<<"\n";
+  //mesh_file<<"Tetrahedra\n";
+  //mesh_file<<T.rows()<<"\n";
+  //mesh_file<<(T.array()+1).eval().format(format)<<"\n";
+  //mesh_file.close();
+
   FILE * mesh_file = fopen(str.c_str(),"w");
   if(NULL==mesh_file)
   {
@@ -105,5 +130,6 @@ IGL_INLINE bool igl::writeMESH(
 
 #ifndef IGL_HEADER_ONLY
 // Explicit template specialization
-template bool igl::writeMESH<Eigen::Matrix<double, -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::MatrixBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::MatrixBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::MatrixBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&);
+//template bool igl::writeMESH<Eigen::Matrix<double, -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::MatrixBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::MatrixBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::MatrixBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&);
+template bool igl::writeMESH<Eigen::Matrix<double, -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> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&);
 #endif

+ 5 - 3
include/igl/writeMESH.h

@@ -18,6 +18,8 @@ namespace igl
   //   V  double matrix of vertex positions  #V by 3
   //   T  #T list of tet indices into vertex positions
   //   F  #F list of face indices into vertex positions
+  //
+  // Known bugs: Holes and regions are not supported
   template <typename Scalar, typename Index>
   IGL_INLINE bool writeMESH(
     const std::string mesh_file_name,
@@ -37,9 +39,9 @@ namespace igl
   template <typename DerivedV, typename DerivedT, typename DerivedF>
   IGL_INLINE bool writeMESH(
     const std::string str,
-    const Eigen::MatrixBase<DerivedV> & V, 
-    const Eigen::MatrixBase<DerivedT> & T,
-    const Eigen::MatrixBase<DerivedF> & F);
+    const Eigen::PlainObjectBase<DerivedV> & V, 
+    const Eigen::PlainObjectBase<DerivedT> & T,
+    const Eigen::PlainObjectBase<DerivedF> & F);
 }
 
 #ifdef IGL_HEADER_ONLY