Browse Source

stl file format

Former-commit-id: 6398310d6a33f437f942eace4e10ef32bbae65b8
Alec Jacobson (jalec 11 years ago
parent
commit
404a719d25

+ 1 - 0
RELEASE_HISTORY.txt

@@ -1,3 +1,4 @@
+0.4.4  STL file format support
 0.4.3  ARAP implementation
 0.4.3  ARAP implementation
 0.4.1  Migrated much of the FAST code including extra for Sifakis' 3x3 svd
 0.4.1  Migrated much of the FAST code including extra for Sifakis' 3x3 svd
 0.4.0  Release under MPL2 license
 0.4.0  Release under MPL2 license

+ 1 - 1
VERSION.txt

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

+ 20 - 0
examples/quicklook-mesh/Info.plist

@@ -13,6 +13,7 @@
 			<array>
 			<array>
 				<string>org.mesh</string>
 				<string>org.mesh</string>
 				<string>org.obj</string>
 				<string>org.obj</string>
+				<string>org.stl</string>
 				<string>org.off</string>
 				<string>org.off</string>
 				<string>org.wrl</string>
 				<string>org.wrl</string>
 			</array>
 			</array>
@@ -102,6 +103,25 @@
 				</array>
 				</array>
 			</dict>
 			</dict>
 		</dict>
 		</dict>
+		<dict>
+			<key>UTTypeConformsTo</key>
+			<array>
+				<string>public.image</string>
+			</array>
+			<key>UTTypeDescription</key>
+			<string>Stereolithography CAD model</string>
+			<key>UTTypeIdentifier</key>
+			<string>org.stl</string>
+			<key>UTTypeReferenceURL</key>
+			<string>http://en.wikipedia.org/wiki/STL_(file_format)</string>
+			<key>UTTypeTagSpecification</key>
+			<dict>
+				<key>public.filename-extension</key>
+				<array>
+					<string>stl</string>
+				</array>
+			</dict>
+		</dict>
 		<dict>
 		<dict>
 			<key>UTTypeConformsTo</key>
 			<key>UTTypeConformsTo</key>
 			<array>
 			<array>

+ 4 - 4
examples/quicklook-mesh/Makefile

@@ -4,10 +4,10 @@
 # include the Foundation headers
 # include the Foundation headers
 #CXX=llvm-g++
 #CXX=llvm-g++
 #C=llvm-gcc
 #C=llvm-gcc
-CXX=clang++
-C=clang
-#CXX=clang++-mp-3.4
-#C=clang-mp-3.4
+#CXX=clang++
+#C=clang
+CXX=clang++-mp-3.4
+C=clang-mp-3.4
 CXXFLAGS += -stdlib=libc++ -std=c++11
 CXXFLAGS += -stdlib=libc++ -std=c++11
 
 
 EIGEN=/opt/local/include/eigen3/
 EIGEN=/opt/local/include/eigen3/

+ 9 - 0
examples/quicklook-mesh/src/render_to_buffer.cpp

@@ -13,6 +13,7 @@ extern "C" {
 #include <igl/material_colors.h>
 #include <igl/material_colors.h>
 #include <igl/pathinfo.h>
 #include <igl/pathinfo.h>
 #include <igl/readOBJ.h>
 #include <igl/readOBJ.h>
+#include <igl/readSTL.h>
 #include <igl/readWRL.h>
 #include <igl/readWRL.h>
 #include <igl/triangulate.h>
 #include <igl/triangulate.h>
 #include <igl/readOFF.h>
 #include <igl/readOFF.h>
@@ -375,6 +376,14 @@ bool render_to_buffer(
       red(width,height,buffer);
       red(width,height,buffer);
       return false;
       return false;
     }
     }
+  }else if(ext == "stl")
+  {
+    // Convert extension to lower case
+    if(!igl::readSTL(filename,vV,vF,vN))
+    {
+      red(width,height,buffer);
+      return false;
+    }
   }else
   }else
   {
   {
     // Convert extension to lower case
     // Convert extension to lower case

+ 1 - 0
file-formats/index.html

@@ -36,6 +36,7 @@
     <li><i>.poly</i> Piecewise-linear complex. This format comes in many similar but importantly different flavors: 
     <li><i>.poly</i> Piecewise-linear complex. This format comes in many similar but importantly different flavors: 
       <a href="https://www.cs.cmu.edu/~quake/triangle.poly.html">triangle's</a>, <a href="http://tetgen.berlios.de/fformats.poly.html">tetgen's</a>, <a href="http://sparse-meshing.com/svr/0.2.1/format-poly.html">pyramid/SVR's</a></li>
       <a href="https://www.cs.cmu.edu/~quake/triangle.poly.html">triangle's</a>, <a href="http://tetgen.berlios.de/fformats.poly.html">tetgen's</a>, <a href="http://sparse-meshing.com/svr/0.2.1/format-poly.html">pyramid/SVR's</a></li>
     <li><a href=./rbr.html>.rbr</a> ASCII files for saving state of ReAntTweakBar</li>
     <li><a href=./rbr.html>.rbr</a> ASCII files for saving state of ReAntTweakBar</li>
+    <li><a href=http://en.wikipedia.org/wiki/STL_(file_format)>.stl</a> 3D Systems' CAD and 3D printing mesh file format. ASCII and binary versions.</li>
     <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=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>
     <li><a href="./tgf.html">.tgf</a> ASCII files for representing control handle graphs</li>
     <li><a href="http://en.wikipedia.org/wiki/VRML#WRL_File_Format">.wrl</a>
     <li><a href="http://en.wikipedia.org/wiki/VRML#WRL_File_Format">.wrl</a>

+ 2 - 2
include/igl/draw_mesh.cpp

@@ -89,9 +89,9 @@ IGL_INLINE void igl::draw_mesh(
   {
   {
     assert(F.maxCoeff() < V.rows());
     assert(F.maxCoeff() < V.rows());
     assert(V.cols() == 3);
     assert(V.cols() == 3);
-    assert(C.rows() == V.rows() || C.rows() == F.rows()*3 || C.size() == 0);
+    assert(rC == rV || rC == rF || rC == rF*3 || C.size() == 0);
     assert(C.cols() == 3 || C.size() == 0);
     assert(C.cols() == 3 || C.size() == 0);
-    assert(N.cols() == 3);
+    assert(N.cols() == 3 || N.size() == 0);
     assert(TC.cols() == 2 || TC.size() == 0);
     assert(TC.cols() == 2 || TC.size() == 0);
   }
   }
   if(W.size()>0)
   if(W.size()>0)

+ 1 - 0
include/igl/project.cpp

@@ -117,6 +117,7 @@ template Eigen::PlainObjectBase<Eigen::Matrix<double, 3, 1, 0, 3, 1> > igl::proj
 template int igl::project<Eigen::Matrix<float, 3, 1, 0, 3, 1>, Eigen::Matrix<float, 3, 1, 0, 3, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> >&);
 template int igl::project<Eigen::Matrix<float, 3, 1, 0, 3, 1>, Eigen::Matrix<float, 3, 1, 0, 3, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> >&);
 template Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > igl::project<Eigen::Matrix<float, 3, 1, 0, 3, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > const&);
 template Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > igl::project<Eigen::Matrix<float, 3, 1, 0, 3, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<float, 3, 1, 0, 3, 1> > const&);
 template Eigen::PlainObjectBase<Eigen::Matrix<double, 1, -1, 1, 1, -1> > igl::project<Eigen::Matrix<double, 1, -1, 1, 1, -1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, 1, -1, 1, 1, -1> > const&);
 template Eigen::PlainObjectBase<Eigen::Matrix<double, 1, -1, 1, 1, -1> > igl::project<Eigen::Matrix<double, 1, -1, 1, 1, -1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, 1, -1, 1, 1, -1> > const&);
+template int igl::project<Eigen::Matrix<double, 1, 3, 1, 1, 3>, Eigen::Matrix<double, 1, 3, 1, 1, 3> >(Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 3, 1, 1, 3> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, 1, 3, 1, 1, 3> >&);
 #endif
 #endif
 
 
 #endif
 #endif

+ 19 - 19
include/igl/readOBJ.cpp

@@ -38,7 +38,7 @@ IGL_INLINE bool igl::readOBJ(
   F.clear();
   F.clear();
   FTC.clear();
   FTC.clear();
   FN.clear();
   FN.clear();
-  
+
   // variables an constants to assist parsing the .obj file
   // variables an constants to assist parsing the .obj file
   // Constant strings to compare against
   // Constant strings to compare against
   std::string v("v");
   std::string v("v");
@@ -46,15 +46,15 @@ IGL_INLINE bool igl::readOBJ(
   std::string vt("vt");
   std::string vt("vt");
   std::string f("f");
   std::string f("f");
   std::string tic_tac_toe("#");
   std::string tic_tac_toe("#");
-#ifndef LINE_MAX
-#  define LINE_MAX 2048
+#ifndef IGL_LINE_MAX
+#  define IGL_LINE_MAX 2048
 #endif
 #endif
-  
-  char line[LINE_MAX];
+
+  char line[IGL_LINE_MAX];
   int line_no = 1;
   int line_no = 1;
-  while (fgets(line, LINE_MAX, obj_file) != NULL) 
+  while (fgets(line, IGL_LINE_MAX, obj_file) != NULL) 
   {
   {
-    char type[LINE_MAX];
+    char type[IGL_LINE_MAX];
     // Read first word containing type
     // Read first word containing type
     if(sscanf(line, "%s",type) == 1)
     if(sscanf(line, "%s",type) == 1)
     {
     {
@@ -123,7 +123,7 @@ IGL_INLINE bool igl::readOBJ(
         std::vector<Index > ftc;
         std::vector<Index > ftc;
         std::vector<Index > fn;
         std::vector<Index > fn;
         // Read each "word" after type
         // Read each "word" after type
-        char word[LINE_MAX];
+        char word[IGL_LINE_MAX];
         int offset;
         int offset;
         while(sscanf(l,"%s%n",word,&offset) == 1)
         while(sscanf(l,"%s%n",word,&offset) == 1)
         {
         {
@@ -196,10 +196,10 @@ IGL_INLINE bool igl::readOBJ(
     line_no++;
     line_no++;
   }
   }
   fclose(obj_file);
   fclose(obj_file);
-  
+
   assert(F.size() == FN.size());
   assert(F.size() == FN.size());
   assert(F.size() == FTC.size());
   assert(F.size() == FTC.size());
-  
+
   return true;
   return true;
 }
 }
 
 
@@ -243,7 +243,7 @@ IGL_INLINE bool igl::readOBJ(
       return false;
       return false;
     }
     }
   }
   }
-  
+
   if(!vFN.empty())
   if(!vFN.empty())
   {
   {
     bool FN_rect = igl::list_to_matrix(vFN,FN);
     bool FN_rect = igl::list_to_matrix(vFN,FN);
@@ -253,10 +253,10 @@ IGL_INLINE bool igl::readOBJ(
       return false;
       return false;
     }
     }
   }
   }
-  
+
   if(!vTC.empty())
   if(!vTC.empty())
   {
   {
-    
+
     bool T_rect = igl::list_to_matrix(vTC,TC);
     bool T_rect = igl::list_to_matrix(vTC,TC);
     if(!T_rect)
     if(!T_rect)
     {
     {
@@ -266,7 +266,7 @@ IGL_INLINE bool igl::readOBJ(
   }
   }
   if(!vFTC.empty())
   if(!vFTC.empty())
   {
   {
-    
+
     bool FTC_rect = igl::list_to_matrix(vFTC,FTC);
     bool FTC_rect = igl::list_to_matrix(vFTC,FTC);
     if(!FTC_rect)
     if(!FTC_rect)
     {
     {
@@ -308,31 +308,31 @@ IGL_INLINE bool igl::readOBJPoly(
     return false;
     return false;
 
 
   F = vF;
   F = vF;
-  
+
   if(!vN.empty())
   if(!vN.empty())
   {
   {
     bool VN_rect = igl::list_to_matrix(vN,CN);
     bool VN_rect = igl::list_to_matrix(vN,CN);
     if(!VN_rect)
     if(!VN_rect)
       return false;
       return false;
   }
   }
-  
+
   if(!vFN.empty())
   if(!vFN.empty())
   {
   {
     bool FN_rect = igl::list_to_matrix(vFN,FN);
     bool FN_rect = igl::list_to_matrix(vFN,FN);
     if(!FN_rect)
     if(!FN_rect)
       return false;
       return false;
   }
   }
-  
+
   if(!vTC.empty())
   if(!vTC.empty())
   {
   {
-    
+
     bool T_rect = igl::list_to_matrix(vTC,TC);
     bool T_rect = igl::list_to_matrix(vTC,TC);
     if(!T_rect)
     if(!T_rect)
       return false;
       return false;
   }
   }
   if(!vFTC.empty())
   if(!vFTC.empty())
   {
   {
-    
+
     bool FTC_rect = igl::list_to_matrix(vFTC,FTC);
     bool FTC_rect = igl::list_to_matrix(vFTC,FTC);
     if(!FTC_rect)
     if(!FTC_rect)
       return false;
       return false;

+ 212 - 0
include/igl/readSTL.cpp

@@ -0,0 +1,212 @@
+#include "readSTL.h"
+#include "list_to_matrix.h"
+
+#include <iostream>
+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))
+  {
+    return false;
+  }
+
+  if(!list_to_matrix(vV,V))
+  {
+    return false;
+  }
+
+  if(!list_to_matrix(vF,F))
+  {
+    return false;
+  }
+
+  if(!list_to_matrix(vN,N))
+  {
+    return false;
+  }
+  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(),"r");
+  if(NULL==stl_file)
+  {
+    fprintf(stderr,"IOError: %s could not be opened...\n",
+            filename.c_str());
+    return false;
+  }
+  V.clear();
+  F.clear();
+  N.clear();
+#ifndef IGL_LINE_MAX
+#  define IGL_LINE_MAX 2048
+#endif
+  char solid[IGL_LINE_MAX];
+  if(fscanf(stl_file,"%s",solid)!=1)
+  {
+    // file too short
+    cerr<<"IOError: "<<filename<<" too short."<<endl;
+    goto close_false;
+  }
+  if(string("solid") == solid)
+  {
+    // Eat file name
+    char name[IGL_LINE_MAX];
+    if(NULL==fgets(name,IGL_LINE_MAX,stl_file))
+    {
+      cerr<<"IOError: "<<filename<<" too short."<<endl;
+      goto close_false;
+    }
+    // ascii
+    while(true)
+    {
+      int ret;
+      char facet[IGL_LINE_MAX],normal[IGL_LINE_MAX];
+      vector<TypeN > n(3);
+      ret = fscanf(stl_file,"%s %s %lg %lg %lg",facet,normal,&n[0],&n[1],&n[2]);
+      if(string("endsolid") == facet)
+      {
+        break;
+      }
+      if(ret != 5 || string("facet") != facet || string("normal") != normal)
+      {
+        cerr<<"IOError: "<<filename<<" bad format (1)."<<endl;
+        goto close_false;
+      }
+      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: "<<filename<<" bad format (2)."<<endl;
+        goto close_false;
+      }
+      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);
+          int ret = fscanf(stl_file,"%lg %lg %lg",&v[0],&v[1],&v[2]);
+          if(ret != 3)
+          {
+            cerr<<"IOError: "<<filename<<" bad format (3)."<<endl;
+            goto close_false;
+          }
+          f.push_back(V.size());
+          V.push_back(v);
+        }else
+        {
+          cerr<<"IOError: "<<filename<<" bad format (4)."<<endl;
+          goto close_false;
+        }
+      }
+      F.push_back(f);
+      char endfacet[IGL_LINE_MAX];
+      ret = fscanf(stl_file,"%s",endfacet);
+      if(ret != 1 || string("endfacet") != endfacet)
+      {
+        cerr<<"IOError: "<<filename<<" bad format (5)."<<endl;
+        goto close_false;
+      }
+    }
+    // read endfacet
+    fclose(stl_file);
+    goto close_true;
+  }else
+  {
+    // Binary
+    fclose(stl_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;
+    }
+    // Read 80 header
+    char header[80];
+    if(fread(header,sizeof(char),80,stl_file)!=80)
+    {
+      cerr<<"IOError: "<<filename<<" bad format (1)."<<endl;
+      goto close_false;
+    }
+    // Read number of triangles
+    unsigned int num_tri;
+    if(fread(&num_tri,sizeof(unsigned int),1,stl_file)!=1)
+    {
+      cerr<<"IOError: "<<filename<<" bad format (2)."<<endl;
+      goto close_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<num_tri;t++)
+    {
+      // Read normal
+      float n[3];
+      if(fread(n,sizeof(float),3,stl_file)!=3)
+      {
+        cerr<<"IOError: "<<filename<<" bad format (3)."<<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: "<<filename<<" bad format (4)."<<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: "<<filename<<" bad format (5)."<<endl;
+        goto close_false;
+      }
+    }
+    goto close_true;
+  }
+close_false:
+  fclose(stl_file);
+  return false;
+close_true:
+  fclose(stl_file);
+  return true;
+}
+
+#ifndef IGL_HEADER_ONLY
+// Explicit template instanciation
+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> >&);
+#endif

+ 57 - 0
include/igl/readSTL.h

@@ -0,0 +1,57 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+// 
+// Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
+// 
+// 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_READSTL_H
+#define IGL_READSTL_H
+#include "igl_inline.h"
+
+#ifndef IGL_NO_EIGEN
+#  include <Eigen/Core>
+#endif
+#include <string>
+#include <vector>
+
+namespace igl 
+{
+  // Read a mesh from an ascii/binary stl file.
+  //
+  // Templates:
+  //   Scalar  type for positions and vectors (will be read as double and cast
+  //     to Scalar)
+  // Inputs:
+  //   filename path to .obj file
+  // Outputs:
+  //   V  double matrix of vertex positions  #F*3 by 3
+  //   F  index matrix of triangle indices #F by 3
+  //   N  double matrix of vertex positions  #F by 3
+  // Returns true on success, false on errors
+  //
+  // Example:
+  //   bool success = readSTL(filename,temp_V,F,N);
+  //   remove_duplicate_vertices(temp_V,0,V,SVI,SVJ);
+  //   for_each(F.data(),F.data()+F.size(),[&SVJ](int & f){f=SVJ(f);});
+  //   writeOBJ("Downloads/cat.obj",V,F);
+  template <typename DerivedV, typename DerivedF, typename DerivedN>
+  IGL_INLINE bool readSTL(
+    const std::string & filename,
+    Eigen::PlainObjectBase<DerivedV> & V,
+    Eigen::PlainObjectBase<DerivedF> & F,
+    Eigen::PlainObjectBase<DerivedN> & N);
+  template <typename TypeV, typename TypeF, typename TypeN>
+  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);
+}
+
+#ifdef IGL_HEADER_ONLY
+#  include "readSTL.cpp"
+#endif
+
+#endif
+

+ 101 - 0
include/igl/writeSTL.cpp

@@ -0,0 +1,101 @@
+#include "writeSTL.h"
+
+template <typename DerivedV, typename DerivedF, typename DerivedN>
+IGL_INLINE bool igl::writeSTL(
+  const std::string & filename,
+  const Eigen::PlainObjectBase<DerivedV> & V,
+  const Eigen::PlainObjectBase<DerivedF> & F,
+  const Eigen::PlainObjectBase<DerivedN> & N,
+  const bool ascii)
+{
+  using namespace std;
+  assert(N.rows() == 0 || F.rows() == N.rows());
+  if(ascii)
+  {
+    FILE * stl_file = fopen(filename.c_str(),"w");
+    if(stl_file == NULL)
+    {
+      cerr<<"IOError: "<<filename<<" could not be opened for writing."<<endl;
+      return false;
+    }
+    fprintf(stl_file,"solid %s\n",filename.c_str());
+    for(int f = 0;f<F.rows();f++)
+    {
+      fprintf(stl_file,"facet normal ");
+      if(N.rows()>0)
+      {
+        fprintf(stl_file,"%e %e %e\n",
+          (float)N(f,0),
+          (float)N(f,1),
+          (float)N(f,2));
+      }else
+      {
+        fprintf(stl_file,"0 0 0\n");
+      }
+      fprintf(stl_file,"outer loop\n");
+      for(int c = 0;c<F.cols();c++)
+      {
+        fprintf(stl_file,"vertex %e %e %e\n",
+          (float)V(F(f,c),0),
+          (float)V(F(f,c),1),
+          (float)V(F(f,c),2));
+      }
+      fprintf(stl_file,"endloop\n");
+      fprintf(stl_file,"endfacet\n");
+    }
+    fprintf(stl_file,"endsolid %s\n",filename.c_str());
+    fclose(stl_file);
+    return true;
+  }else
+  {
+    FILE * stl_file = fopen(filename.c_str(),"wb");
+    if(stl_file == NULL)
+    {
+      cerr<<"IOError: "<<filename<<" could not be opened for writing."<<endl;
+      return false;
+    }
+    // Write unused 80-char header
+    for(char h = 0;h<80;h++)
+    {
+      fwrite(&h,sizeof(char),1,stl_file);
+    }
+    // Write number of triangles
+    unsigned int num_tri = F.rows();
+    fwrite(&num_tri,sizeof(unsigned int),1,stl_file);
+    assert(F.cols() == 3);
+    // Write each triangle
+    for(int f = 0;f<F.rows();f++)
+    {
+      vector<float> n(3,0);
+      if(N.rows() > 0)
+      {
+        n[0] = N(f,0);
+        n[1] = N(f,1);
+        n[2] = N(f,2);
+      }
+      fwrite(&n[0],sizeof(float),3,stl_file);
+      for(int c = 0;c<3;c++)
+      {
+        vector<float> v(3);
+        v[0] = V(F(f,c),0);
+        v[1] = V(F(f,c),1);
+        v[2] = V(F(f,c),2);
+        fwrite(&v[0],sizeof(float),3,stl_file);
+      }
+      unsigned short att_count = 0;
+      fwrite(&att_count,sizeof(unsigned short),1,stl_file);
+    }
+    fclose(stl_file);
+    return true;
+  }
+}
+
+template <typename DerivedV, typename DerivedF>
+IGL_INLINE bool igl::writeSTL(
+  const std::string & filename,
+  const Eigen::PlainObjectBase<DerivedV> & V,
+  const Eigen::PlainObjectBase<DerivedF> & F,
+  const bool ascii)
+{
+  return writeSTL(filename,V,F, Eigen::PlainObjectBase<DerivedV>(), ascii);
+}

+ 52 - 0
include/igl/writeSTL.h

@@ -0,0 +1,52 @@
+// This file is part of libigl, a simple c++ geometry processing library.
+// 
+// Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
+// 
+// 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_WRITESTL_H
+#define IGL_WRITESTL_H
+#include "igl_inline.h"
+
+#ifndef IGL_NO_EIGEN
+#  include <Eigen/Core>
+#endif
+#include <string>
+#include <vector>
+
+namespace igl 
+{
+  // Write a mesh to an stl file.
+  //
+  // Templates:
+  //   Scalar  type for positions and vectors (will be read as double and cast
+  //     to Scalar)
+  // Inputs:
+  //   filename path to .obj file
+  //   V  double matrix of vertex positions  #F*3 by 3
+  //   F  index matrix of triangle indices #F by 3
+  //   N  double matrix of vertex positions  #F by 3
+  //   asci  write ascii file {true}
+  // Returns true on success, false on errors
+  //
+  template <typename DerivedV, typename DerivedF, typename DerivedN>
+  IGL_INLINE bool writeSTL(
+    const std::string & filename,
+    const Eigen::PlainObjectBase<DerivedV> & V,
+    const Eigen::PlainObjectBase<DerivedF> & F,
+    const Eigen::PlainObjectBase<DerivedN> & N,
+    const bool ascii=true);
+  template <typename DerivedV, typename DerivedF>
+  IGL_INLINE bool writeSTL(
+    const std::string & filename,
+    const Eigen::PlainObjectBase<DerivedV> & V,
+    const Eigen::PlainObjectBase<DerivedF> & F,
+    const bool ascii=true);
+}
+
+#ifdef IGL_HEADER_ONLY
+#  include "writeSTL.cpp"
+#endif
+
+#endif

+ 9 - 3
matlab-to-eigen.html

@@ -6,7 +6,7 @@
     <table>
     <table>
       <tr class="header">
       <tr class="header">
         <th>MATLAB</th>
         <th>MATLAB</th>
-        <th>Eigen</th>
+        <th>Eigen with libigl</th>
         <th>Notes</th>
         <th>Notes</th>
       </tr>
       </tr>
 
 
@@ -202,13 +202,19 @@
       </tr>
       </tr>
 
 
       <tr class=d0>
       <tr class=d0>
-        <td><pre><code>B = IM(A)</code></pre></td>
+        <td><pre><code>B = IM(A)
+
+A = IM(A);
+  </code></pre></td>
         <td><pre><code>B = A.unaryExpr(bind1st(mem_fun( 
         <td><pre><code>B = A.unaryExpr(bind1st(mem_fun( 
   static_cast&lt;VectorXi::Scalar&amp;(VectorXi::*)(VectorXi::Index)&gt;
   static_cast&lt;VectorXi::Scalar&amp;(VectorXi::*)(VectorXi::Index)&gt;
   (&amp;VectorXi::operator())), &amp;IM)).eval();
   (&amp;VectorXi::operator())), &amp;IM)).eval();
+
+for_each(A.data(),A.data()+A.size(),[&amp;IM](int &amp; a){a=IM(a);});
   </code></pre></td>
   </code></pre></td>
         <td>Where IM is an "index map" column vector and A is an arbitrary
         <td>Where IM is an "index map" column vector and A is an arbitrary
-        matrix. The <code>.eval()</code> is not necessary if A != B</td>
+        matrix. The <code>.eval()</code> is not necessary if A != B, but then
+        the second option should be used.</td>
       </tr>
       </tr>
 
 
       <tr class=d1>
       <tr class=d1>