Browse Source

Update STL reader. (#1628)

* Update STL reader.

* Constexpr stuff.
Jérémie Dumas 5 years ago
parent
commit
1e905cba3d

+ 29 - 0
include/igl/file_utils.cpp

@@ -0,0 +1,29 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2020 Jérémie Dumas <[email protected]>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License
+// v. 2.0. If a copy of the MPL was not distributed with this file, You can
+// obtain one at http://mozilla.org/MPL/2.0/.
+#include "file_utils.h"
+
+#include <cstring>
+
+namespace igl {
+
+IGL_INLINE void read_file_binary(FILE *fp,
+                                 std::vector<uint8_t> &fileBufferBytes) {
+  if (!ferror(fp)) {
+    fseek(fp, 0, SEEK_END);
+    size_t sizeBytes = ftell(fp);
+    fseek(fp, 0, SEEK_SET);
+    fileBufferBytes.resize(sizeBytes);
+
+    if (fread((char *)fileBufferBytes.data(), 1, sizeBytes, fp) == sizeBytes)
+      return;
+  }
+
+  throw std::runtime_error("error reading from file");
+}
+
+}

+ 62 - 0
include/igl/file_utils.h

@@ -0,0 +1,62 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2020 Jérémie Dumas <[email protected]>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License
+// v. 2.0. If a copy of the MPL was not distributed with this file, You can
+// obtain one at http://mozilla.org/MPL/2.0/.
+#ifndef IGL_FILE_UTILS_H
+#define IGL_FILE_UTILS_H
+
+#include "igl_inline.h"
+
+#include <streambuf>
+#include <istream>
+#include <string>
+#include <vector>
+
+namespace igl {
+
+IGL_INLINE void read_file_binary(FILE *fp,
+                                 std::vector<uint8_t> &fileBufferBytes);
+
+struct file_memory_buffer : public std::streambuf {
+  char *p_start{nullptr};
+  char *p_end{nullptr};
+  size_t size;
+
+  file_memory_buffer(char const *first_elem, size_t size)
+      : p_start(const_cast<char *>(first_elem)), p_end(p_start + size),
+        size(size) {
+    setg(p_start, p_start, p_end);
+  }
+
+  pos_type seekoff(off_type off, std::ios_base::seekdir dir,
+                   std::ios_base::openmode which) override {
+    if (dir == std::ios_base::cur) {
+      gbump(static_cast<int>(off));
+    } else {
+      setg(p_start, (dir == std::ios_base::beg ? p_start : p_end) + off, p_end);
+    }
+    return gptr() - p_start;
+  }
+
+  pos_type seekpos(pos_type pos, std::ios_base::openmode which) override {
+    return seekoff(pos, std::ios_base::beg, which);
+  }
+};
+
+struct file_memory_stream : virtual file_memory_buffer, public std::istream {
+  file_memory_stream(char const *first_elem, size_t size)
+      : file_memory_buffer(first_elem, size), std::istream(
+                                                  static_cast<std::streambuf *>(
+                                                      this)) {}
+};
+
+} // namespace igl
+
+#ifndef IGL_STATIC_LIBRARY
+#include "file_utils.cpp"
+#endif
+
+#endif

+ 45 - 0
include/igl/list_to_matrix.cpp

@@ -52,6 +52,39 @@ IGL_INLINE bool igl::list_to_matrix(const std::vector<std::vector<T > > & V,Eige
   return true;
   return true;
 }
 }
 
 
+template <typename T, size_t N, typename Derived>
+IGL_INLINE bool igl::list_to_matrix(const std::vector<std::array<T, N> > & V,Eigen::PlainObjectBase<Derived>& M)
+{
+  // number of rows
+  int m = V.size();
+  if(m == 0)
+  {
+    M.resize(
+            Derived::RowsAtCompileTime>=0?Derived::RowsAtCompileTime:0
+            ,
+            Derived::ColsAtCompileTime>=0?Derived::ColsAtCompileTime:0
+            );
+    return true;
+  }
+  // number of columns
+  int n = static_cast<int>(N);
+  assert(n != -1);
+  // Resize output
+  M.resize(m,n);
+
+  // Loop over rows
+  for(int i = 0;i<m;i++)
+  {
+    // Loop over cols
+    for(int j = 0;j<n;j++)
+    {
+      M(i,j) = V[i][j];
+    }
+  }
+
+  return true;
+}
+
 template <typename T, typename Derived>
 template <typename T, typename Derived>
 IGL_INLINE bool igl::list_to_matrix(
 IGL_INLINE bool igl::list_to_matrix(
   const std::vector<std::vector<T > > & V,
   const std::vector<std::vector<T > > & V,
@@ -172,6 +205,18 @@ template bool igl::list_to_matrix<int, Eigen::Matrix<int, -1, 3, 1, -1, 3> >(std
 template bool igl::list_to_matrix<unsigned long, Eigen::Matrix<long, -1, 1, 0, -1, 1> >(std::vector<unsigned long, std::allocator<unsigned long> > const&, Eigen::PlainObjectBase<Eigen::Matrix<long, -1, 1, 0, -1, 1> >&);
 template bool igl::list_to_matrix<unsigned long, Eigen::Matrix<long, -1, 1, 0, -1, 1> >(std::vector<unsigned long, std::allocator<unsigned long> > const&, Eigen::PlainObjectBase<Eigen::Matrix<long, -1, 1, 0, -1, 1> >&);
 template bool igl::list_to_matrix<bool, Eigen::Matrix<bool, -1, 1, 0, -1, 1> >(std::vector<bool, std::allocator<bool> > const&, Eigen::PlainObjectBase<Eigen::Matrix<bool, -1, 1, 0, -1, 1> >&);
 template bool igl::list_to_matrix<bool, Eigen::Matrix<bool, -1, 1, 0, -1, 1> >(std::vector<bool, std::allocator<bool> > const&, Eigen::PlainObjectBase<Eigen::Matrix<bool, -1, 1, 0, -1, 1> >&);
 
 
+template bool igl::list_to_matrix<double, 3ul, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::vector<std::array<double, 3ul>, std::allocator<std::array<double, 3ul> > > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
+template bool igl::list_to_matrix<double, 3ul, Eigen::Matrix<double, -1, -1, 1, -1, -1> >(std::vector<std::array<double, 3ul>, std::allocator<std::array<double, 3ul> > > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 1, -1, -1> >&);
+template bool igl::list_to_matrix<double, 3ul, Eigen::Matrix<double, -1, 3, 0, -1, 3> >(std::vector<std::array<double, 3ul>, std::allocator<std::array<double, 3ul> > > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, 3, 0, -1, 3> >&);
+template bool igl::list_to_matrix<double, 3ul, Eigen::Matrix<double, -1, 3, 1, -1, 3> >(std::vector<std::array<double, 3ul>, std::allocator<std::array<double, 3ul> > > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, 3, 1, -1, 3> >&);
+template bool igl::list_to_matrix<float, 3ul, Eigen::Matrix<float, -1, -1, 0, -1, -1> >(std::vector<std::array<float, 3ul>, std::allocator<std::array<float, 3ul> > > const&, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, -1, 0, -1, -1> >&);
+template bool igl::list_to_matrix<float, 3ul, Eigen::Matrix<float, -1, 3, 0, -1, 3> >(std::vector<std::array<float, 3ul>, std::allocator<std::array<float, 3ul> > > const&, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, 3, 0, -1, 3> >&);
+template bool igl::list_to_matrix<float, 3ul, Eigen::Matrix<float, -1, 3, 1, -1, 3> >(std::vector<std::array<float, 3ul>, std::allocator<std::array<float, 3ul> > > const&, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, 3, 1, -1, 3> >&);
+template bool igl::list_to_matrix<int, 3ul, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(std::vector<std::array<int, 3ul>, std::allocator<std::array<int, 3ul> > > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&);
+template bool igl::list_to_matrix<int, 3ul, Eigen::Matrix<int, -1, 3, 0, -1, 3> >(std::vector<std::array<int, 3ul>, std::allocator<std::array<int, 3ul> > > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 3, 0, -1, 3> >&);
+template bool igl::list_to_matrix<int, 3ul, Eigen::Matrix<int, -1, 3, 1, -1, 3> >(std::vector<std::array<int, 3ul>, std::allocator<std::array<int, 3ul> > > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 3, 1, -1, 3> >&);
+template bool igl::list_to_matrix<unsigned int, 3ul, Eigen::Matrix<unsigned int, -1, 3, 1, -1, 3> >(std::vector<std::array<unsigned int, 3ul>, std::allocator<std::array<unsigned int, 3ul> > > const&, Eigen::PlainObjectBase<Eigen::Matrix<unsigned int, -1, 3, 1, -1, 3> >&);
+
 #ifdef WIN32
 #ifdef WIN32
 template bool igl::list_to_matrix<double, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::vector<double, std::allocator<double> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
 template bool igl::list_to_matrix<double, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::vector<double, std::allocator<double> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
 template bool igl::list_to_matrix<double, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::vector<double, std::allocator<double> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
 template bool igl::list_to_matrix<double, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::vector<double, std::allocator<double> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);

+ 7 - 0
include/igl/list_to_matrix.h

@@ -9,6 +9,7 @@
 #define IGL_LIST_TO_MATRIX_H
 #define IGL_LIST_TO_MATRIX_H
 #include "igl_inline.h"
 #include "igl_inline.h"
 #include <vector>
 #include <vector>
+#include <array>
 #include <Eigen/Core>
 #include <Eigen/Core>
 
 
 namespace igl
 namespace igl
@@ -28,6 +29,12 @@ namespace igl
   IGL_INLINE bool list_to_matrix(
   IGL_INLINE bool list_to_matrix(
     const std::vector<std::vector<T > > & V,
     const std::vector<std::vector<T > > & V,
     Eigen::PlainObjectBase<Derived>& M);
     Eigen::PlainObjectBase<Derived>& M);
+
+  template <typename T, size_t N, typename Derived>
+  IGL_INLINE bool list_to_matrix(
+    const std::vector<std::array<T, N> > & V,
+    Eigen::PlainObjectBase<Derived>& M);
+
   // Convert a list of row vectors of `n` or less to a matrix and pad on
   // Convert a list of row vectors of `n` or less to a matrix and pad on
   // the right with `padding`:
   // the right with `padding`:
   //
   //

+ 2 - 49
include/igl/readPLY.cpp

@@ -6,6 +6,7 @@
 #include <Eigen/Core>
 #include <Eigen/Core>
 
 
 #include "tinyply.h"
 #include "tinyply.h"
+#include "file_utils.h"
 
 
 
 
 namespace igl
 namespace igl
@@ -137,54 +138,6 @@ IGL_INLINE bool tinyply_tristrips_to_faces(
   }
   }
 }
 }
 
 
-
-inline void read_file_binary(FILE *fp, std::vector<uint8_t>& fileBufferBytes)
-{
-    if (!ferror(fp))
-    {
-        fseek(fp,0,SEEK_END);
-        size_t sizeBytes = ftell(fp);
-        fseek(fp,0,SEEK_SET);
-        fileBufferBytes.resize(sizeBytes);
-
-        if(fread((char*)fileBufferBytes.data(), 1, sizeBytes,fp)==sizeBytes)
-          return;
-    }
-
-    throw std::runtime_error("error reading from file");
-}
-
-struct ply_memory_buffer : public std::streambuf
-{
-    char * p_start {nullptr};
-    char * p_end {nullptr};
-    size_t size;
-
-    ply_memory_buffer(char const * first_elem, size_t size)
-        : p_start(const_cast<char*>(first_elem)), p_end(p_start + size), size(size)
-    {
-        setg(p_start, p_start, p_end);
-    }
-
-    pos_type seekoff(off_type off, std::ios_base::seekdir dir, std::ios_base::openmode which) override
-    {
-        if (dir == std::ios_base::cur) gbump(static_cast<int>(off));
-        else setg(p_start, (dir == std::ios_base::beg ? p_start : p_end) + off, p_end);
-        return gptr() - p_start;
-    }
-
-    pos_type seekpos(pos_type pos, std::ios_base::openmode which) override
-    {
-        return seekoff(pos, std::ios_base::beg, which);
-    }
-};
-
-struct ply_memory_stream : virtual ply_memory_buffer, public std::istream
-{
-    ply_memory_stream(char const * first_elem, size_t size)
-        : ply_memory_buffer(first_elem, size), std::istream(static_cast<std::streambuf*>(this)) {}
-};
-
 template <
 template <
   typename DerivedV,
   typename DerivedV,
   typename DerivedF,
   typename DerivedF,
@@ -218,7 +171,7 @@ IGL_INLINE bool readPLY(
   {
   {
     std::vector<uint8_t> fileBufferBytes;
     std::vector<uint8_t> fileBufferBytes;
     read_file_binary(fp,fileBufferBytes);
     read_file_binary(fp,fileBufferBytes);
-    ply_memory_stream stream((char*)fileBufferBytes.data(), fileBufferBytes.size());
+    file_memory_stream stream((char*)fileBufferBytes.data(), fileBufferBytes.size());
     return readPLY(stream,V,F,E,N,UV,VD,Vheader,FD,Fheader,ED,Eheader,comments);
     return readPLY(stream,V,F,E,N,UV,VD,Vheader,FD,Fheader,ED,Eheader,comments);
   }
   }
   catch(const std::exception& e)
   catch(const std::exception& e)

+ 273 - 241
include/igl/readSTL.cpp

@@ -1,290 +1,322 @@
 // This file is part of libigl, a simple c++ geometry processing library.
 // This file is part of libigl, a simple c++ geometry processing library.
-// 
+//
 // Copyright (C) 2014 Alec Jacobson <[email protected]>
 // Copyright (C) 2014 Alec Jacobson <[email protected]>
-// 
-// This Source Code Form is subject to the terms of the Mozilla Public License 
-// v. 2.0. If a copy of the MPL was not distributed with this file, You can 
+// Copyright (C) 2018 Qingnan Zhou <[email protected]>
+// Copyright (C) 2020 Jérémie Dumas <[email protected]>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License
+// v. 2.0. If a copy of the MPL was not distributed with this file, You can
 // obtain one at http://mozilla.org/MPL/2.0/.
 // obtain one at http://mozilla.org/MPL/2.0/.
 #include "readSTL.h"
 #include "readSTL.h"
 #include "list_to_matrix.h"
 #include "list_to_matrix.h"
+#include "string_utils.h"
+#include "file_utils.h"
 
 
 #include <iostream>
 #include <iostream>
+
+namespace igl {
+
 template <typename DerivedV, typename DerivedF, typename DerivedN>
 template <typename DerivedV, typename DerivedF, typename DerivedN>
-IGL_INLINE bool igl::readSTL(
-  const std::string & filename,
-  Eigen::PlainObjectBase<DerivedV> & V,
-  Eigen::PlainObjectBase<DerivedF> & F,
-  Eigen::PlainObjectBase<DerivedN> & N)
-{
-  using namespace std;
-  vector<vector<typename DerivedV::Scalar> > vV;
-  vector<vector<typename DerivedN::Scalar> > vN;
-  vector<vector<typename DerivedF::Scalar> > vF;
-  if(!readSTL(filename,vV,vF,vN))
-  {
+IGL_INLINE bool readSTL(std::istream &input,
+                        Eigen::PlainObjectBase<DerivedV> &V,
+                        Eigen::PlainObjectBase<DerivedF> &F,
+                        Eigen::PlainObjectBase<DerivedN> &N) {
+  std::vector<std::array<typename DerivedV::Scalar, 3>> vV;
+  std::vector<std::array<typename DerivedN::Scalar, 3>> vN;
+  std::vector<std::array<typename DerivedF::Scalar, 3>> vF;
+  if (!readSTL(input, vV, vF, vN)) {
     return false;
     return false;
   }
   }
 
 
-  if(!list_to_matrix(vV,V))
-  {
+  if (!list_to_matrix(vV, V)) {
     return false;
     return false;
   }
   }
 
 
-  if(!list_to_matrix(vF,F))
-  {
+  if (!list_to_matrix(vF, F)) {
     return false;
     return false;
   }
   }
 
 
-  if(!list_to_matrix(vN,N))
-  {
+  if (!list_to_matrix(vN, N)) {
     return false;
     return false;
   }
   }
   return true;
   return true;
 }
 }
 
 
-template <typename TypeV, typename TypeF, typename TypeN>
-IGL_INLINE bool igl::readSTL(
-  const std::string & filename,
-  std::vector<std::vector<TypeV> > & V,
-  std::vector<std::vector<TypeF> > & F,
-  std::vector<std::vector<TypeN> > & N)
-{
-  using namespace std;
-  // Should test for ascii
-
-  // Open file, and check for error
-  FILE * stl_file = fopen(filename.c_str(),"rb");
-  if(NULL==stl_file)
-  {
-    fprintf(stderr,"IOError: %s could not be opened...\n",
-            filename.c_str());
-    return false;
-  }
-  return readSTL(stl_file,V,F,N);
-}
+IGL_INLINE bool is_stl_binary(std::istream &input) {
+  std::streampos start_pos = input.tellg();
 
 
-template <typename TypeV, typename TypeF, typename TypeN>
-IGL_INLINE bool igl::readSTL(
-  FILE * stl_file, 
-  std::vector<std::vector<TypeV> > & V,
-  std::vector<std::vector<TypeF> > & F,
-  std::vector<std::vector<TypeN> > & N)
-{
-  using namespace std;
-  //stl_file = freopen(NULL,"rb",stl_file);
-  if(NULL==stl_file)
-  {
-    fprintf(stderr,"IOError: stl file could not be reopened as binary (1) ...\n");
+  constexpr size_t HEADER_SIZE = 80;
+  char header[HEADER_SIZE];
+  input.read(header, HEADER_SIZE);
+  if (!starts_with(header, "solid")) {
+    return true;
+  }
+  if (!input.good()) {
     return false;
     return false;
   }
   }
 
 
-  V.clear();
-  F.clear();
-  N.clear();
+  // Check if filesize matches the number of faces claimed.
+  char buf[4];
+  input.read(buf, 4);
+  size_t num_faces = *reinterpret_cast<uint32_t *>(buf);
+  input.seekg(0, input.end);
+  size_t file_size = input.tellg();
 
 
+  input.seekg(start_pos);
 
 
-  // Specifically 80 character header
-  char header[80];
-  char solid[80] = {0};
-  bool is_ascii = true;
-  if(fread(header,1,80,stl_file) != 80)
-  {
-    cerr<<"IOError: too short (1)."<<endl;
-    goto close_false;
+  if (file_size == 80 + 4 + (4 * 12 + 2) * num_faces) {
+    return true;
+  } else {
+    return false;
   }
   }
-  sscanf(header,"%79s",solid);
-  if(string("solid") != solid)
-  {
-    // definitely **not** ascii 
-    is_ascii = false;
-  }else
-  {
-    // might still be binary
-    char buf[4];
-    if(fread(buf,1,4,stl_file) != 4)
-    {
-      cerr<<"IOError: too short (3)."<<endl;
-      goto close_false;
-    }
-    size_t num_faces = *reinterpret_cast<unsigned int*>(buf);
-    fseek(stl_file,0,SEEK_END);
-    int file_size = ftell(stl_file);
-    if(file_size == 80 + 4 + (4*12 + 2) * num_faces)
-    {
-      is_ascii = false;
-    }else
-    {
-      is_ascii = true;
-    }
+}
+
+template <typename TypeV, typename TypeF, typename TypeN>
+IGL_INLINE bool read_stl_ascii(std::istream &input,
+                               std::vector<std::array<TypeV, 3>> &V,
+                               std::vector<std::array<TypeF, 3>> &F,
+                               std::vector<std::array<TypeN, 3>> &N) {
+  constexpr size_t LINE_SIZE = 256;
+  char line[LINE_SIZE];
+  bool success = true;
+
+  if (!input) {
+    throw std::runtime_error("Failed to open file");
   }
   }
 
 
-  if(is_ascii)
-  {
-    // Rewind to end of header
-    //stl_file = fopen(filename.c_str(),"r");
-    //stl_file = freopen(NULL,"r",stl_file);
-    fseek(stl_file, 0, SEEK_SET);
-    if(NULL==stl_file)
-    {
-      fprintf(stderr,"IOError: stl file could not be reopened as ascii ...\n");
+  // skip header line.
+  input.getline(line, LINE_SIZE);
+
+  auto parse_ascii_normal = [&N](const char *line) {
+    double x, y, z;
+    size_t n = sscanf(line, " facet normal %lf %lf %lf", &x, &y, &z);
+    assert(n == 3);
+    if (n != 3) {
       return false;
       return false;
     }
     }
-    // Read 80 header
-    // Eat file name
-#ifndef IGL_LINE_MAX
-#  define IGL_LINE_MAX 2048
-#endif
-    char name[IGL_LINE_MAX];
-    if(NULL==fgets(name,IGL_LINE_MAX,stl_file))
-    {
-      cerr<<"IOError: ascii too short (2)."<<endl;
-      goto close_false;
+
+    N.push_back({{static_cast<TypeN>(x), static_cast<TypeN>(y),
+                  static_cast<TypeN>(z)}});
+    return true;
+  };
+
+  auto parse_ascii_vertex = [&V](const char *line) {
+    double x, y, z;
+    size_t n = sscanf(line, " vertex %lf %lf %lf", &x, &y, &z);
+    assert(n == 3);
+    if (n != 3) {
+      return false;
     }
     }
-    // ascii
-    while(true)
-    {
-      int ret;
-      char facet[IGL_LINE_MAX],normal[IGL_LINE_MAX];
-      vector<TypeN > n(3);
-      double nd[3];
-      ret = fscanf(stl_file,"%s %s %lg %lg %lg",facet,normal,nd,nd+1,nd+2);
-      if(string("endsolid") == facet)
-      {
-        break;
-      }
-      if(ret != 5 || 
-          !(string("facet") == facet || 
-          string("faced") == facet) ||
-          string("normal") != normal)
-      {
-        cout<<"facet: "<<facet<<endl;
-        cout<<"normal: "<<normal<<endl;
-        cerr<<"IOError: bad format (1)."<<endl;
-        goto close_false;
-      }
-      // copy casts to Type
-      n[0] = nd[0]; n[1] = nd[1]; n[2] = nd[2];
-      N.push_back(n);
-      char outer[IGL_LINE_MAX], loop[IGL_LINE_MAX];
-      ret = fscanf(stl_file,"%s %s",outer,loop);
-      if(ret != 2 || string("outer") != outer || string("loop") != loop)
-      {
-        cerr<<"IOError: bad format (2)."<<endl;
-        goto close_false;
+
+    V.push_back({{static_cast<TypeV>(x), static_cast<TypeV>(y),
+                  static_cast<TypeV>(z)}});
+    return true;
+  };
+
+  auto parse_ascii_facet = [&parse_ascii_vertex, &parse_ascii_normal](std::istream &fin) {
+    constexpr size_t LINE_SIZE = 256;
+    constexpr size_t WORD_SIZE = 128;
+    char line[LINE_SIZE];
+    char first_word[WORD_SIZE];
+    const char *face_begin = "facet";
+    const char *face_end = "endfacet";
+    const char *loop_begin = "outer";
+    const char *loop_end = "endloop";
+    const char *vertex_flag = "vertex";
+
+    bool reading_facet = false;
+    bool reading_loop = false;
+    bool success = true;
+    size_t num_vts = 0;
+    while (!fin.eof()) {
+      fin.getline(line, LINE_SIZE);
+      size_t n = sscanf(line, " %s", first_word);
+      if (n == 0)
+        continue;
+      if (starts_with(first_word, face_begin)) {
+        success = parse_ascii_normal(line);
+        assert(success);
+        reading_facet = true;
+      } else if (starts_with(first_word, face_end)) {
+        assert(reading_facet);
+        reading_facet = false;
+      } else if (starts_with(first_word, loop_begin)) {
+        reading_loop = true;
+      } else if (starts_with(first_word, loop_end)) {
+        assert(reading_loop);
+        reading_loop = false;
+      } else if (starts_with(first_word, vertex_flag)) {
+        assert(reading_facet);
+        assert(reading_loop);
+        success = parse_ascii_vertex(line);
+        assert(success);
+        num_vts += 1;
       }
       }
-      vector<TypeF> f;
-      while(true)
-      {
-        char word[IGL_LINE_MAX];
-        int ret = fscanf(stl_file,"%s",word);
-        if(ret == 1 && string("endloop") == word)
-        {
-          break;
-        }else if(ret == 1 && string("vertex") == word)
-        {
-          vector<TypeV> v(3);
-          double vd[3];
-          int ret = fscanf(stl_file,"%lg %lg %lg",vd,vd+1,vd+2);
-          if(ret != 3)
-          {
-            cerr<<"IOError: bad format (3)."<<endl;
-            goto close_false;
-          }
-          f.push_back(V.size());
-          // copy casts to Type
-          v[0] = vd[0]; v[1] = vd[1]; v[2] = vd[2];
-          V.push_back(v);
-        }else
-        {
-          cerr<<"IOError: bad format (4)."<<endl;
-          goto close_false;
-        }
+      if (!success) {
+        return false;
       }
       }
-      F.push_back(f);
-      char endfacet[IGL_LINE_MAX];
-      ret = fscanf(stl_file,"%s",endfacet);
-      if(ret != 1 || string("endfacet") != endfacet)
-      {
-        cerr<<"IOError: bad format (5)."<<endl;
-        goto close_false;
+      if (!reading_facet) {
+        break;
       }
       }
     }
     }
-    // read endfacet
-    goto close_true;
-  }else
-  {
-    // Binary
-    //stl_file = freopen(NULL,"rb",stl_file);
-    fseek(stl_file, 0, SEEK_SET);
-    // Read 80 header
-    char header[80];
-    if(fread(header,sizeof(char),80,stl_file)!=80)
-    {
-      cerr<<"IOError: bad format (6)."<<endl;
-      goto close_false;
+    if (num_vts == 0) {
+      return true;
     }
     }
-    // Read number of triangles
-    unsigned int num_tri;
-    if(fread(&num_tri,sizeof(unsigned int),1,stl_file)!=1)
-    {
-      cerr<<"IOError: bad format (7)."<<endl;
-      goto close_false;
+    assert(num_vts == 3);
+    if (num_vts != 3) {
+      std::cerr << "Warning: mesh contain face made of " << num_vts
+                << " vertices" << std::endl;
+      return false;
     }
     }
-    V.resize(num_tri*3,vector<TypeV >(3,0));
-    N.resize(num_tri,vector<TypeN >(3,0));
-    F.resize(num_tri,vector<TypeF >(3,0));
-    for(int t = 0;t<(int)num_tri;t++)
-    {
-      // Read normal
-      float n[3];
-      if(fread(n,sizeof(float),3,stl_file)!=3)
-      {
-        cerr<<"IOError: bad format (8)."<<endl;
-        goto close_false;
-      }
-      // Read each vertex
-      for(int c = 0;c<3;c++)
-      {
-        F[t][c] = 3*t+c;
-        N[t][c] = n[c];
-        float v[3];
-        if(fread(v,sizeof(float),3,stl_file)!=3)
-        {
-          cerr<<"IOError: bad format (9)."<<endl;
-          goto close_false;
-        }
-        V[3*t+c][0] = v[0];
-        V[3*t+c][1] = v[1];
-        V[3*t+c][2] = v[2];
-      }
-      // Read attribute size
-      unsigned short att_count;
-      if(fread(&att_count,sizeof(unsigned short),1,stl_file)!=1)
-      {
-        cerr<<"IOError: bad format (10)."<<endl;
-        goto close_false;
+    return true;
+  };
+
+  while (!input.eof()) {
+    success = parse_ascii_facet(input);
+    if (!success) {
+      return false;
+    }
+  }
+
+  F.resize(V.size() / 3);
+    for (size_t f = 0; f < F.size(); ++f) {
+    auto v = static_cast<TypeF>(f * 3);
+    F[f] = {{v, v + 1, v + 2}};
+  }
+  return success;
+}
+
+template <typename TypeV, typename TypeF, typename TypeN>
+IGL_INLINE bool read_stl_binary(std::istream &input,
+                                std::vector<std::array<TypeV, 3>> &V,
+                                std::vector<std::array<TypeF, 3>> &F,
+                                std::vector<std::array<TypeN, 3>> &N) {
+  if (!input) {
+    throw std::runtime_error("Failed to open file");
+  }
+
+  constexpr size_t FLOAT_SIZE = sizeof(float);
+  static_assert(FLOAT_SIZE == 4, "float type is not 4 bytes");
+  constexpr size_t LINE_SIZE = 256;
+  char buf[LINE_SIZE];
+
+  // 80 bytes header, no data significance.
+  input.read(buf, 80);
+  if (!input.good()) {
+    throw std::runtime_error("Unable to parse STL header.");
+  }
+
+  input.read(buf, 4);
+  const size_t num_faces = *reinterpret_cast<uint32_t *>(buf);
+  if (!input.good()) {
+    throw std::runtime_error("Unable to parse STL number of faces.");
+  }
+
+  for (size_t i = 0; i < num_faces; i++) {
+    // Parse normal
+    input.read(buf, FLOAT_SIZE * 3);
+    auto nx = static_cast<TypeN>(*reinterpret_cast<float *>(buf));
+    auto ny = static_cast<TypeN>(*reinterpret_cast<float *>(buf + FLOAT_SIZE));
+    auto nz =
+        static_cast<TypeN>(*reinterpret_cast<float *>(buf + FLOAT_SIZE * 2));
+    assert(input.good());
+
+    // vertex 1
+    input.read(buf, FLOAT_SIZE * 3);
+    auto v1x = static_cast<TypeV>(*reinterpret_cast<float *>(buf));
+    auto v1y = static_cast<TypeV>(*reinterpret_cast<float *>(buf + FLOAT_SIZE));
+    auto v1z =
+        static_cast<TypeV>(*reinterpret_cast<float *>(buf + FLOAT_SIZE * 2));
+    assert(input.good());
+
+    // vertex 2
+    input.read(buf, FLOAT_SIZE * 3);
+    auto v2x = static_cast<TypeV>(*reinterpret_cast<float *>(buf));
+    auto v2y = static_cast<TypeV>(*reinterpret_cast<float *>(buf + FLOAT_SIZE));
+    auto v2z =
+        static_cast<TypeV>(*reinterpret_cast<float *>(buf + FLOAT_SIZE * 2));
+    assert(input.good());
+
+    // vertex 3
+    input.read(buf, FLOAT_SIZE * 3);
+    auto v3x = static_cast<TypeV>(*reinterpret_cast<float *>(buf));
+    auto v3y = static_cast<TypeV>(*reinterpret_cast<float *>(buf + FLOAT_SIZE));
+    auto v3z =
+        static_cast<TypeV>(*reinterpret_cast<float *>(buf + FLOAT_SIZE * 2));
+    assert(input.good());
+
+    // attribute (2 bytes), not sure what purpose they serve.
+    input.read(buf, 2);
+
+    N.push_back({{nx, ny, nz}});
+    V.push_back({{v1x, v1y, v1z}});
+    V.push_back({{v2x, v2y, v2z}});
+    V.push_back({{v3x, v3y, v3z}});
+
+    assert(input.good());
+    if (!input.good()) {
+      std::stringstream err_msg;
+      err_msg << "Failed to parse face " << i << " from STL file";
+      throw std::runtime_error(err_msg.str());
+    }
+  }
+  std::for_each(V.begin(), V.end(), [](const std::array<TypeV, 3> &v) {
+    for (auto x : v) {
+      if (!std::isfinite(x)) {
+        throw std::runtime_error("NaN or Inf detected in input file.");
       }
       }
     }
     }
-    goto close_true;
+  });
+
+  if (!V.empty()) {
+    F.resize(V.size() / 3);
+    for (size_t f = 0; f < F.size(); ++f) {
+      auto v = static_cast<TypeF>(f * 3);
+      F[f] = {{v, v + 1, v + 2}};
+    }
   }
   }
-close_false:
-  fclose(stl_file);
-  return false;
-close_true:
-  fclose(stl_file);
+
   return true;
   return true;
 }
 }
 
 
+template <typename TypeV, typename TypeF, typename TypeN>
+IGL_INLINE bool readSTL(std::istream &input,
+                        std::vector<std::array<TypeV, 3>> &V,
+                        std::vector<std::array<TypeF, 3>> &F,
+                        std::vector<std::array<TypeN, 3>> &N) {
+  bool success = false;
+  if (is_stl_binary(input)) {
+    success = read_stl_binary(input, V, F, N);
+  } else {
+    success = read_stl_ascii(input, V, F, N);
+  }
+  return success;
+}
+
+template <typename DerivedV, typename DerivedF, typename DerivedN>
+IGL_INLINE bool readSTL(
+  FILE * fp,
+  Eigen::PlainObjectBase<DerivedV> & V,
+  Eigen::PlainObjectBase<DerivedF> & F,
+  Eigen::PlainObjectBase<DerivedN> & N)
+{
+  std::vector<uint8_t> fileBufferBytes;
+  read_file_binary(fp,fileBufferBytes);
+  file_memory_stream stream((char*)fileBufferBytes.data(), fileBufferBytes.size());
+  return readSTL(stream, V, F, N);
+}
+
+} // namespace igl
+
 #ifdef IGL_STATIC_LIBRARY
 #ifdef IGL_STATIC_LIBRARY
 // Explicit template instantiation
 // Explicit template instantiation
-// generated by autoexplicit.sh
-template bool igl::readSTL<Eigen::Matrix<double, -1, -1, 1, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
-// generated by autoexplicit.sh
-template bool igl::readSTL<Eigen::Matrix<float, -1, 3, 1, -1, 3>, Eigen::Matrix<unsigned int, -1, 3, 1, -1, 3>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, 3, 1, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<unsigned int, -1, 3, 1, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
-// generated by autoexplicit.sh
-template bool igl::readSTL<Eigen::Matrix<double, -1, 3, 0, -1, 3>, Eigen::Matrix<int, -1, 3, 0, -1, 3>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
-// generated by autoexplicit.sh
-template bool igl::readSTL<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
-template bool igl::readSTL<Eigen::Matrix<float, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<float, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, -1, 0, -1, -1> >&);
-template bool igl::readSTL<Eigen::Matrix<double, -1, 3, 1, -1, 3>, Eigen::Matrix<int, -1, 3, 1, -1, 3>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, 3, 1, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 3, 1, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
+template bool igl::readSTL<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(FILE*, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
+template bool igl::readSTL<Eigen::Matrix<double, -1, -1, 1, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(FILE*, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 1, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
+template bool igl::readSTL<Eigen::Matrix<double, -1, 3, 0, -1, 3>, Eigen::Matrix<int, -1, 3, 0, -1, 3>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(FILE*, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
+template bool igl::readSTL<Eigen::Matrix<double, -1, 3, 1, -1, 3>, Eigen::Matrix<int, -1, 3, 1, -1, 3>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(FILE*, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, 3, 1, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 3, 1, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
+template bool igl::readSTL<Eigen::Matrix<float, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(FILE*, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
+template bool igl::readSTL<Eigen::Matrix<float, -1, 3, 0, -1, 3>, Eigen::Matrix<int, -1, 3, 0, -1, 3>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(FILE*, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 3, 0, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
+template bool igl::readSTL<Eigen::Matrix<float, -1, 3, 1, -1, 3>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(FILE*, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, 3, 1, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
+template bool igl::readSTL<Eigen::Matrix<float, -1, 3, 1, -1, 3>, Eigen::Matrix<int, -1, 3, 1, -1, 3>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(FILE*, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, 3, 1, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, 3, 1, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
+template bool igl::readSTL<Eigen::Matrix<float, -1, 3, 1, -1, 3>, Eigen::Matrix<unsigned int, -1, 3, 1, -1, 3>, Eigen::Matrix<double, -1, -1, 0, -1, -1> >(FILE*, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, 3, 1, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<unsigned int, -1, 3, 1, -1, 3> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&);
 #endif
 #endif

+ 20 - 20
include/igl/readSTL.h

@@ -1,9 +1,10 @@
 // This file is part of libigl, a simple c++ geometry processing library.
 // This file is part of libigl, a simple c++ geometry processing library.
-// 
+//
 // Copyright (C) 2013 Alec Jacobson <[email protected]>
 // Copyright (C) 2013 Alec Jacobson <[email protected]>
-// 
-// This Source Code Form is subject to the terms of the Mozilla Public License 
-// v. 2.0. If a copy of the MPL was not distributed with this file, You can 
+// Copyright (C) 2020 Jérémie Dumas <[email protected]>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License
+// v. 2.0. If a copy of the MPL was not distributed with this file, You can
 // obtain one at http://mozilla.org/MPL/2.0/.
 // obtain one at http://mozilla.org/MPL/2.0/.
 #ifndef IGL_READSTL_H
 #ifndef IGL_READSTL_H
 #define IGL_READSTL_H
 #define IGL_READSTL_H
@@ -13,10 +14,11 @@
 #  include <Eigen/Core>
 #  include <Eigen/Core>
 #endif
 #endif
 #include <string>
 #include <string>
-#include <cstdio>
+#include <istream>
 #include <vector>
 #include <vector>
+#include <array>
 
 
-namespace igl 
+namespace igl
 {
 {
   // Read a mesh from an ascii/binary stl file.
   // Read a mesh from an ascii/binary stl file.
   //
   //
@@ -38,26 +40,24 @@ namespace igl
   //   writeOBJ("Downloads/cat.obj",V,F);
   //   writeOBJ("Downloads/cat.obj",V,F);
   template <typename DerivedV, typename DerivedF, typename DerivedN>
   template <typename DerivedV, typename DerivedF, typename DerivedN>
   IGL_INLINE bool readSTL(
   IGL_INLINE bool readSTL(
-    const std::string & filename,
+    std::istream &input,
     Eigen::PlainObjectBase<DerivedV> & V,
     Eigen::PlainObjectBase<DerivedV> & V,
     Eigen::PlainObjectBase<DerivedF> & F,
     Eigen::PlainObjectBase<DerivedF> & F,
     Eigen::PlainObjectBase<DerivedN> & N);
     Eigen::PlainObjectBase<DerivedN> & N);
-  // Inputs:
-  //   stl_file  pointer to already opened .stl file 
-  // Outputs:
-  //   stl_file  closed file
+
   template <typename TypeV, typename TypeF, typename TypeN>
   template <typename TypeV, typename TypeF, typename TypeN>
   IGL_INLINE bool readSTL(
   IGL_INLINE bool readSTL(
-    FILE * stl_file, 
-    std::vector<std::vector<TypeV> > & V,
-    std::vector<std::vector<TypeF> > & F,
-    std::vector<std::vector<TypeN> > & N);
-  template <typename TypeV, typename TypeF, typename TypeN>
+    std::istream &input,
+    std::vector<std::array<TypeV, 3> > & V,
+    std::vector<std::array<TypeF, 3> > & F,
+    std::vector<std::array<TypeN, 3> > & N);
+
+  template <typename DerivedV, typename DerivedF, typename DerivedN>
   IGL_INLINE bool readSTL(
   IGL_INLINE bool readSTL(
-    const std::string & filename,
-    std::vector<std::vector<TypeV> > & V,
-    std::vector<std::vector<TypeF> > & F,
-    std::vector<std::vector<TypeN> > & N);
+    FILE * fp,
+    Eigen::PlainObjectBase<DerivedV> & V,
+    Eigen::PlainObjectBase<DerivedF> & F,
+    Eigen::PlainObjectBase<DerivedN> & N);
 }
 }
 
 
 #ifndef IGL_STATIC_LIBRARY
 #ifndef IGL_STATIC_LIBRARY

+ 3 - 2
include/igl/read_triangle_mesh.cpp

@@ -103,6 +103,7 @@ IGL_INLINE bool igl::read_triangle_mesh(
 {
 {
   using namespace std;
   using namespace std;
   using namespace Eigen;
   using namespace Eigen;
+  Eigen::MatrixXd N;
   vector<vector<double > > vV,vN,vTC,vC;
   vector<vector<double > > vV,vN,vTC,vC;
   vector<vector<int > > vF,vFTC,vFN;
   vector<vector<int > > vF,vFTC,vFN;
   vector<tuple<string, int, int>> FM;
   vector<tuple<string, int, int>> FM;
@@ -140,10 +141,10 @@ IGL_INLINE bool igl::read_triangle_mesh(
   }else if(ext == "ply")
   }else if(ext == "ply")
   {
   {
     return readPLY(fp, V, F);
     return readPLY(fp, V, F);
-    
+
   }else if(ext == "stl")
   }else if(ext == "stl")
   {
   {
-    if(!readSTL(fp,vV,vF,vN))
+    if(!readSTL(fp,V,F,N))
     {
     {
       return false;
       return false;
     }
     }

+ 22 - 0
include/igl/string_utils.cpp

@@ -0,0 +1,22 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2020 Jérémie Dumas <[email protected]>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License
+// v. 2.0. If a copy of the MPL was not distributed with this file, You can
+// obtain one at http://mozilla.org/MPL/2.0/.
+#include "string_utils.h"
+
+#include <cstring>
+
+namespace igl {
+
+IGL_INLINE bool starts_with(const std::string &str, const std::string &prefix) {
+  return (str.rfind(prefix, 0) == 0);
+}
+
+IGL_INLINE bool starts_with(const char *str, const char* prefix) {
+  return strncmp(prefix, str, strlen(prefix)) == 0;
+}
+
+}

+ 27 - 0
include/igl/string_utils.h

@@ -0,0 +1,27 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+//
+// Copyright (C) 2020 Jérémie Dumas <[email protected]>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public License
+// v. 2.0. If a copy of the MPL was not distributed with this file, You can
+// obtain one at http://mozilla.org/MPL/2.0/.
+#ifndef IGL_STRING_UTILS_H
+#define IGL_STRING_UTILS_H
+
+#include "igl_inline.h"
+
+#include <string>
+
+namespace igl {
+
+IGL_INLINE bool starts_with(const std::string &str, const std::string &prefix);
+
+IGL_INLINE bool starts_with(const char *str, const char* prefix);
+
+}
+
+#ifndef IGL_STATIC_LIBRARY
+#  include "string_utils.cpp"
+#endif
+
+#endif