cgv
stl_reader.h
Go to the documentation of this file.
1 /*
2  Copyright (c) 2018, Sebastian Reiter (s.b.reiter@gmail.com)
3  All rights reserved.
4 
5  Redistribution and use in source and binary forms, with or without
6  modification, are permitted provided that the following conditions are met:
7  * Redistributions of source code must retain the above copyright
8  notice, this list of conditions and the following disclaimer.
9  * Redistributions in binary form must reproduce the above copyright
10  notice, this list of conditions and the following disclaimer in the
11  documentation and/or other materials provided with the distribution.
12 
13  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17  ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24 
25 
127 #ifndef __H__STL_READER
128 #define __H__STL_READER
129 
130 #include <algorithm>
131 #include <exception>
132 #include <fstream>
133 #include <sstream>
134 #include <vector>
135 
136 #ifdef STL_READER_NO_EXCEPTIONS
137  #define STL_READER_THROW(msg) return false;
138  #define STL_READER_COND_THROW(cond, msg) if(cond) return false;
139 #else
140  #define STL_READER_THROW(msg) {std::stringstream ss; ss << msg; throw(std::runtime_error(ss.str()));}
142 
144  #define STL_READER_COND_THROW(cond, msg) if(cond){std::stringstream ss; ss << msg; throw(std::runtime_error(ss.str()));}
145 #endif
146 
147 
148 namespace stl_reader {
149 
151 
191 template <class TNumberContainer1, class TNumberContainer2,
192  class TIndexContainer1, class TIndexContainer2>
193 bool ReadStlFile(const char* filename,
194  TNumberContainer1& coordsOut,
195  TNumberContainer2& normalsOut,
196  TIndexContainer1& trisOut,
197  TIndexContainer2& solidRangesOut);
198 
199 
201 
204 template <class TNumberContainer1, class TNumberContainer2,
205  class TIndexContainer1, class TIndexContainer2>
206 bool ReadStlFile_ASCII(const char* filename,
207  TNumberContainer1& coordsOut,
208  TNumberContainer2& normalsOut,
209  TIndexContainer1& trisOut,
210  TIndexContainer2& solidRangesOut);
211 
213 
217 template <class TNumberContainer1, class TNumberContainer2,
218  class TIndexContainer1, class TIndexContainer2>
219 bool ReadStlFile_BINARY(const char* filename,
220  TNumberContainer1& coordsOut,
221  TNumberContainer2& normalsOut,
222  TIndexContainer1& trisOut,
223  TIndexContainer2& solidRangesOut);
224 
226 
230 inline bool StlFileHasASCIIFormat(const char* filename);
231 
232 
234 template <class TNumber = float, class TIndex = unsigned int>
235 class StlMesh {
236 public:
239  {
240  solids.resize (2, 0);
241  }
242 
244 
245  StlMesh (const char* filename)
246  {
247  read_file (filename);
248  }
249 
250  StlMesh (const std::string& filename)
251  {
252  read_file (filename);
253  }
256 
258  bool read_file (const char* filename)
259  {
260  bool res = false;
261 
262  #ifndef STL_READER_NO_EXCEPTIONS
263  try {
264  #endif
265 
266  res = ReadStlFile (filename, coords, normals, tris, solids);
267 
268  #ifndef STL_READER_NO_EXCEPTIONS
269  } catch (std::exception& e) {
270  #else
271  if (!res) {
272  #endif
273 
274  coords.clear ();
275  normals.clear ();
276  tris.clear ();
277  solids.clear ();
278  STL_READER_THROW (e.what());
279  }
280 
281  return res;
282  }
283 
284  bool read_file (const std::string& filename)
285  {
286  return read_file (filename.c_str());
287  }
290  size_t num_vrts () const
292  {
293  return coords.size() / 3;
294  }
295 
297  const TNumber* vrt_coords (const size_t vi) const
298  {
299  return &coords[vi * 3];
300  }
301 
303  size_t num_tris () const
304  {
305  return tris.size() / 3;
306  }
307 
309  const TIndex* tri_corner_inds (const size_t ti) const
310  {
311  return &tris [ti * 3];
312  }
313 
315  const TIndex tri_corner_ind (const size_t ti, const size_t ci) const
316  {
317  return tris [ti * 3 + ci];
318  }
319 
327  const TNumber* tri_corner_coords (const size_t ti, const size_t ci) const
328  {
329  return &coords[tri_corner_ind(ti, ci) * 3];
330  }
331 
333  const TNumber* tri_normal (const size_t ti) const
334  {
335  return &normals [ti * 3];
336  }
337 
339 
344  size_t num_solids () const
345  {
346  if(solids.empty ())
347  return 0;
348  return solids.size () - 1;
349  }
350 
352  TIndex solid_tris_begin (const size_t si) const
353  {
354  return solids [si];
355  }
356 
358  TIndex solid_tris_end (const size_t si) const
359  {
360  return solids [si + 1];
361  }
362 
364 
366  const TNumber* raw_coords () const
367  {
368  if(coords.empty())
369  return NULL;
370  return &coords[0];
371  }
372 
374 
376  const TNumber* raw_normals () const
377  {
378  if(normals.empty())
379  return NULL;
380  return &normals[0];
381  }
382 
384 
386  const TIndex* raw_tris () const
387  {
388  if(tris.empty())
389  return NULL;
390  return &tris[0];
391  }
392 
394 
396  const TIndex* raw_solids () const
397  {
398  if(solids.empty())
399  return NULL;
400  return &solids[0];
401  }
402 
403 private:
404  std::vector<TNumber> coords;
405  std::vector<TNumber> normals;
406  std::vector<TIndex> tris;
407  std::vector<TIndex> solids;
408 };
409 
410 
412 // IMPLEMENTATION
414 
415 
416 namespace stl_reader_impl {
417 
418  // a coordinate triple with an additional index. The index is required
419  // for RemoveDoubles, so that triangles can be reindexed properly.
420  template <typename number_t, typename index_t>
421  struct CoordWithIndex {
422  number_t data[3];
423  index_t index;
424 
425  bool operator == (const CoordWithIndex& c) const
426  {
427  return (c[0] == data[0]) && (c[1] == data[1]) && (c[2] == data[2]);
428  }
429 
430  bool operator != (const CoordWithIndex& c) const
431  {
432  return (c[0] != data[0]) || (c[1] != data[1]) || (c[2] != data[2]);
433  }
434 
435  bool operator < (const CoordWithIndex& c) const
436  {
437  return (data[0] < c[0])
438  || (data[0] == c[0] && data[1] < c[1])
439  || (data[0] == c[0] && data[1] == c[1] && data[2] < c[2]);
440  }
441 
442  inline number_t& operator [] (const size_t i) {return data[i];}
443  inline number_t operator [] (const size_t i) const {return data[i];}
444  };
445 
446  // sorts the array coordsWithIndexInOut and copies unique indices to coordsOut.
447  // Triangle-corners are re-indexed on the fly and degenerated triangles are removed.
448  template <class TNumberContainer, class TIndexContainer>
449  void RemoveDoubles (TNumberContainer& uniqueCoordsOut,
450  TIndexContainer& trisInOut,
451  std::vector <CoordWithIndex<
452  typename TNumberContainer::value_type,
453  typename TIndexContainer::value_type> >
454  &coordsWithIndexInOut)
455  {
456  using namespace std;
457 
458  typedef typename TNumberContainer::value_type number_t;
459  typedef typename TIndexContainer::value_type index_t;
460 
461  sort (coordsWithIndexInOut.begin(), coordsWithIndexInOut.end());
462 
463  // first count unique indices
464  index_t numUnique = 1;
465  for(size_t i = 1; i < coordsWithIndexInOut.size(); ++i){
466  if(coordsWithIndexInOut[i] != coordsWithIndexInOut[i - 1])
467  ++numUnique;
468  }
469 
470  uniqueCoordsOut.resize (numUnique * 3);
471  vector<index_t> newIndex (coordsWithIndexInOut.size());
472 
473  // copy unique coordinates to 'uniqueCoordsOut' and create an index-map
474  // 'newIndex', which allows to re-index triangles later on.
475  index_t curInd = 0;
476  newIndex[0] = 0;
477  for(index_t i = 0; i < 3; ++i)
478  uniqueCoordsOut[i] = coordsWithIndexInOut[0][i];
479 
480  for(size_t i = 1; i < coordsWithIndexInOut.size(); ++i){
481  const CoordWithIndex <number_t, index_t> c = coordsWithIndexInOut[i];
482  if(c != coordsWithIndexInOut[i - 1]){
483  ++curInd;
484  for(index_t j = 0; j < 3; ++j)
485  uniqueCoordsOut[curInd * 3 + j] = coordsWithIndexInOut[i][j];
486  }
487 
488  newIndex[c.index] = static_cast<index_t> (curInd);
489  }
490 
491  // re-index triangles, so that they refer to 'uniqueCoordsOut'
492  // make sure to only add triangles which refer to three different indices
493  index_t numUniqueTriInds = 0;
494  for(index_t i = 0; i < trisInOut.size(); i+=3){
495  int ni[3];
496  for(int j = 0; j < 3; ++j)
497  ni[j] = newIndex[trisInOut[i+j]];
498 
499  if((ni[0] != ni[1]) && (ni[0] != ni[2]) && (ni[1] != ni[2])){
500  for(int j = 0; j < 3; ++j)
501  trisInOut[numUniqueTriInds + j] = ni[j];
502  numUniqueTriInds += 3;
503  }
504  }
505 
506  if(numUniqueTriInds < trisInOut.size())
507  trisInOut.resize (numUniqueTriInds);
508  }
509 }// end of namespace stl_reader_impl
510 
511 
512 template <class TNumberContainer1, class TNumberContainer2,
513  class TIndexContainer1, class TIndexContainer2>
514 bool ReadStlFile(const char* filename,
515  TNumberContainer1& coordsOut,
516  TNumberContainer2& normalsOut,
517  TIndexContainer1& trisOut,
518  TIndexContainer2& solidRangesOut)
519 {
520  if(StlFileHasASCIIFormat(filename))
521  return ReadStlFile_ASCII(filename, coordsOut, normalsOut, trisOut, solidRangesOut);
522  else
523  return ReadStlFile_BINARY(filename, coordsOut, normalsOut, trisOut, solidRangesOut);
524 }
525 
526 
527 template <class TNumberContainer1, class TNumberContainer2,
528  class TIndexContainer1, class TIndexContainer2>
529 bool ReadStlFile_ASCII(const char* filename,
530  TNumberContainer1& coordsOut,
531  TNumberContainer2& normalsOut,
532  TIndexContainer1& trisOut,
533  TIndexContainer2& solidRangesOut)
534 {
535  using namespace std;
536  using namespace stl_reader_impl;
537 
538  typedef typename TNumberContainer1::value_type number_t;
539  typedef typename TIndexContainer1::value_type index_t;
540 
541  coordsOut.clear();
542  normalsOut.clear();
543  trisOut.clear();
544  solidRangesOut.clear();
545 
546  ifstream in(filename);
547  STL_READER_COND_THROW(!in, "Couldn't open file " << filename);
548 
549  vector<CoordWithIndex <number_t, index_t> > coordsWithIndex;
550 
551  string buffer;
552  vector<string> tokens;
553  int lineCount = 1;
554  int maxNumTokens = 0;
555  size_t numFaceVrts = 0;
556 
557  while(!(in.eof() || in.fail()))
558  {
559  // read the line and tokenize.
560  // In order to reuse memory in between lines, 'tokens' won't be cleared.
561  // Instead we count the number of tokens using 'tokenCount'.
562  getline(in, buffer);
563 
564  istringstream line(buffer);
565  int tokenCount = 0;
566  while(!(line.eof() || line.fail())){
567  if(tokenCount >= maxNumTokens){
568  maxNumTokens = tokenCount + 1;
569  tokens.resize(maxNumTokens);
570  }
571  line >> tokens[tokenCount];
572  ++tokenCount;
573  }
574 
575  if(tokenCount > 0)
576  {
577  string& tok = tokens[0];
578  if(tok.compare("vertex") == 0){
579  if(tokenCount < 4){
580  STL_READER_THROW("ERROR while reading from " << filename <<
581  ": vertex not specified correctly in line " << lineCount);
582  }
583 
584  // read the position
585  CoordWithIndex <number_t, index_t> c;
586  for(size_t i = 0; i < 3; ++i)
587  c[i] = static_cast<number_t> (atof(tokens[i+1].c_str()));
588  c.index = static_cast<index_t>(coordsWithIndex.size());
589  coordsWithIndex.push_back(c);
590  ++numFaceVrts;
591  }
592  else if(tok.compare("facet") == 0)
593  {
594  STL_READER_COND_THROW(tokenCount < 5,
595  "ERROR while reading from " << filename <<
596  ": triangle not specified correctly in line " << lineCount);
597 
598  STL_READER_COND_THROW(tokens[1].compare("normal") != 0,
599  "ERROR while reading from " << filename <<
600  ": Missing normal specifier in line " << lineCount);
601 
602  // read the normal
603  for(size_t i = 0; i < 3; ++i)
604  normalsOut.push_back (static_cast<number_t> (atof(tokens[i+2].c_str())));
605 
606  numFaceVrts = 0;
607  }
608  else if(tok.compare("outer") == 0){
609  STL_READER_COND_THROW ((tokenCount < 2) || (tokens[1].compare("loop") != 0),
610  "ERROR while reading from " << filename <<
611  ": expecting outer loop in line " << lineCount);
612  }
613  else if(tok.compare("endfacet") == 0){
614  STL_READER_COND_THROW(numFaceVrts != 3,
615  "ERROR while reading from " << filename <<
616  ": bad number of vertices specified for face in line " << lineCount);
617 
618  trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 3));
619  trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 2));
620  trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 1));
621  }
622  else if(tok.compare("solid") == 0){
623  solidRangesOut.push_back(static_cast<index_t> (trisOut.size() / 3));
624  }
625  }
626  lineCount++;
627  }
628 
629  solidRangesOut.push_back(static_cast<index_t> (trisOut.size() / 3));
630 
631  RemoveDoubles (coordsOut, trisOut, coordsWithIndex);
632 
633  return true;
634 }
635 
636 
637 template <class TNumberContainer1, class TNumberContainer2,
638  class TIndexContainer1, class TIndexContainer2>
639 bool ReadStlFile_BINARY(const char* filename,
640  TNumberContainer1& coordsOut,
641  TNumberContainer2& normalsOut,
642  TIndexContainer1& trisOut,
643  TIndexContainer2& solidRangesOut)
644 {
645  using namespace std;
646  using namespace stl_reader_impl;
647 
648  typedef typename TNumberContainer1::value_type number_t;
649  typedef typename TIndexContainer1::value_type index_t;
650 
651  coordsOut.clear();
652  normalsOut.clear();
653  trisOut.clear();
654  solidRangesOut.clear();
655 
656  ifstream in(filename, ios::binary);
657  STL_READER_COND_THROW(!in, "Couldnt open file " << filename);
658 
659  char stl_header[80];
660  in.read(stl_header, 80);
661  STL_READER_COND_THROW(!in, "Error while parsing binary stl header in file " << filename);
662 
663  unsigned int numTris = 0;
664  in.read((char*)&numTris, 4);
665  STL_READER_COND_THROW(!in, "Couldnt determine number of triangles in binary stl file " << filename);
666 
667  vector<CoordWithIndex <number_t, index_t> > coordsWithIndex;
668 
669  for(unsigned int tri = 0; tri < numTris; ++tri){
670  float d[12];
671  in.read((char*)d, 12 * 4);
672  STL_READER_COND_THROW(!in, "Error while parsing trianlge in binary stl file " << filename);
673 
674  for(int i = 0; i < 3; ++i)
675  normalsOut.push_back (d[i]);
676 
677  for(size_t ivrt = 1; ivrt < 4; ++ivrt){
678  CoordWithIndex <number_t, index_t> c;
679  for(size_t i = 0; i < 3; ++i)
680  c[i] = d[ivrt * 3 + i];
681  c.index = static_cast<index_t>(coordsWithIndex.size());
682  coordsWithIndex.push_back(c);
683  }
684 
685  trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 3));
686  trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 2));
687  trisOut.push_back(static_cast<index_t> (coordsWithIndex.size() - 1));
688 
689  char addData[2];
690  in.read(addData, 2);
691  STL_READER_COND_THROW(!in, "Error while parsing additional triangle data in binary stl file " << filename);
692  }
693 
694  solidRangesOut.push_back(0);
695  solidRangesOut.push_back(static_cast<index_t> (trisOut.size() / 3));
696 
697  RemoveDoubles (coordsOut, trisOut, coordsWithIndex);
698 
699  return true;
700 }
701 
702 
703 inline bool StlFileHasASCIIFormat(const char* filename)
704 {
705  using namespace std;
706  ifstream in(filename);
707  STL_READER_COND_THROW(!in, "Couldnt open file " << filename);
708 
709  char chars [256];
710  in.read (chars, 256);
711  string buffer (chars, in.gcount());
712  transform(buffer.begin(), buffer.end(), buffer.begin(), ::tolower);
713  return buffer.find ("solid") != string::npos &&
714  buffer.find ("\n") != string::npos &&
715  buffer.find ("facet") != string::npos &&
716  buffer.find ("normal") != string::npos;
717 }
718 
719 } // end of namespace stl_reader
720 
721 #endif //__H__STL_READER
stl_reader::StlMesh::tri_corner_inds
const TIndex * tri_corner_inds(const size_t ti) const
returns an array of 3 indices, one for each corner vertex of the triangle
Definition: stl_reader.h:309
stl_reader::ReadStlFile_BINARY
bool ReadStlFile_BINARY(const char *filename, TNumberContainer1 &coordsOut, TNumberContainer2 &normalsOut, TIndexContainer1 &trisOut, TIndexContainer2 &solidRangesOut)
Reads a binary stl file into several arrays.
Definition: stl_reader.h:639
STL_READER_COND_THROW
#define STL_READER_COND_THROW(cond, msg)
Throws an std::runtime_error with the given message, if the given condition evaluates to true.
Definition: stl_reader.h:144
stl_reader::StlMesh::num_vrts
size_t num_vrts() const
returns the number of vertices in the mesh
Definition: stl_reader.h:291
stl_reader::StlMesh::tri_corner_coords
const TNumber * tri_corner_coords(const size_t ti, const size_t ci) const
returns an array of 3 floating point values, one for each coordinate of the specified corner of the s...
Definition: stl_reader.h:327
stl_reader::StlMesh::solid_tris_begin
TIndex solid_tris_begin(const size_t si) const
returns the index of the first triangle in the given solid
Definition: stl_reader.h:352
STL_READER_THROW
#define STL_READER_THROW(msg)
Throws an std::runtime_error with the given message.
Definition: stl_reader.h:141
stl_reader::StlMesh::StlMesh
StlMesh()
initializes an empty mesh
Definition: stl_reader.h:238
stl_reader::StlMesh::solid_tris_end
TIndex solid_tris_end(const size_t si) const
returns the index of the triangle behind the last triangle in the given solid
Definition: stl_reader.h:358
stl_reader::StlMesh::raw_normals
const TNumber * raw_normals() const
returns a pointer to the normal array, containing num_tris()*3 entries.
Definition: stl_reader.h:376
stl_reader::StlMesh::raw_coords
const TNumber * raw_coords() const
returns a pointer to the coordinate array, containing num_vrts()*3 entries.
Definition: stl_reader.h:366
stl_reader::StlMesh::tri_normal
const TNumber * tri_normal(const size_t ti) const
returns an array of 3 floating point values defining the normal of a tri
Definition: stl_reader.h:333
stl_reader::StlMesh::vrt_coords
const TNumber * vrt_coords(const size_t vi) const
returns an array of 3 floating point values, one for each coordinate of the vertex
Definition: stl_reader.h:297
stl_reader::StlMesh::raw_solids
const TIndex * raw_solids() const
returns a pointer to the solids array, containing num_solids()+1 entries.
Definition: stl_reader.h:396
stl_reader::StlMesh
convenience mesh class which makes accessing the stl data more easy
Definition: stl_reader.h:235
stl_reader::StlMesh::num_solids
size_t num_solids() const
returns the number of solids of the mesh
Definition: stl_reader.h:344
stl_reader::ReadStlFile_ASCII
bool ReadStlFile_ASCII(const char *filename, TNumberContainer1 &coordsOut, TNumberContainer2 &normalsOut, TIndexContainer1 &trisOut, TIndexContainer2 &solidRangesOut)
Reads an ASCII stl file into several arrays.
Definition: stl_reader.h:529
stl_reader::StlMesh::StlMesh
StlMesh(const char *filename)
initializes the mesh from the stl-file specified through filename
Definition: stl_reader.h:245
stl_reader::StlMesh::num_tris
size_t num_tris() const
returns the number of triangles in the mesh
Definition: stl_reader.h:303
stl_reader::StlMesh::raw_tris
const TIndex * raw_tris() const
returns a pointer to the triangle array, containing num_tris()*3 entries.
Definition: stl_reader.h:386
stl_reader::ReadStlFile
bool ReadStlFile(const char *filename, TNumberContainer1 &coordsOut, TNumberContainer2 &normalsOut, TIndexContainer1 &trisOut, TIndexContainer2 &solidRangesOut)
Reads an ASCII or binary stl file into several arrays.
Definition: stl_reader.h:514
stl_reader::StlFileHasASCIIFormat
bool StlFileHasASCIIFormat(const char *filename)
Determines whether a stl file has ASCII format.
Definition: stl_reader.h:703
stl_reader::StlMesh::tri_corner_ind
const TIndex tri_corner_ind(const size_t ti, const size_t ci) const
returns the index of the corner with index 0<=ci<3 of triangle ti
Definition: stl_reader.h:315
stl_reader::StlMesh::read_file
bool read_file(const char *filename)
fills the mesh with the contents of the specified stl-file
Definition: stl_reader.h:258