Developer Documentation
OBJReader.cc
1 /* ========================================================================= *
2  * *
3  * OpenMesh *
4  * Copyright (c) 2001-2015, RWTH-Aachen University *
5  * Department of Computer Graphics and Multimedia *
6  * All rights reserved. *
7  * www.openmesh.org *
8  * *
9  *---------------------------------------------------------------------------*
10  * This file is part of OpenMesh. *
11  *---------------------------------------------------------------------------*
12  * *
13  * Redistribution and use in source and binary forms, with or without *
14  * modification, are permitted provided that the following conditions *
15  * are met: *
16  * *
17  * 1. Redistributions of source code must retain the above copyright notice, *
18  * this list of conditions and the following disclaimer. *
19  * *
20  * 2. Redistributions in binary form must reproduce the above copyright *
21  * notice, this list of conditions and the following disclaimer in the *
22  * documentation and/or other materials provided with the distribution. *
23  * *
24  * 3. Neither the name of the copyright holder nor the names of its *
25  * contributors may be used to endorse or promote products derived from *
26  * this software without specific prior written permission. *
27  * *
28  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
29  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *
30  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
31  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER *
32  * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, *
33  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
34  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR *
35  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF *
36  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING *
37  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS *
38  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
39  * *
40  * ========================================================================= */
41 
42 
43 
44 
45 //== INCLUDES =================================================================
46 
47 
48 // OpenMesh
49 #include <OpenMesh/Core/IO/reader/OBJReader.hh>
50 #include <OpenMesh/Core/IO/IOManager.hh>
51 #include <OpenMesh/Core/Utils/vector_cast.hh>
52 #include <OpenMesh/Core/Utils/color_cast.hh>
53 // STL
54 #if defined(OM_CC_MIPS)
55 # include <ctype.h>
57 #elif defined(_STLPORT_VERSION) && (_STLPORT_VERSION==0x460)
58 # include <cctype>
59 #else
60 using std::isspace;
61 #endif
62 
63 #ifndef WIN32
64 #endif
65 
66 #include <fstream>
67 
68 //=== NAMESPACES ==============================================================
69 
70 
71 namespace OpenMesh {
72 namespace IO {
73 
74 
75 //=== INSTANCIATE =============================================================
76 
77 
78 _OBJReader_ __OBJReaderInstance;
79 _OBJReader_& OBJReader() { return __OBJReaderInstance; }
80 
81 
82 //=== IMPLEMENTATION ==========================================================
83 
84 //-----------------------------------------------------------------------------
85 
86 void trimString( std::string& _string) {
87  // Trim Both leading and trailing spaces
88 
89  size_t start = _string.find_first_not_of(" \t\r\n");
90  size_t end = _string.find_last_not_of(" \t\r\n");
91 
92  if(( std::string::npos == start ) || ( std::string::npos == end))
93  _string = "";
94  else
95  _string = _string.substr( start, end-start+1 );
96 }
97 
98 //-----------------------------------------------------------------------------
99 
100 // remove duplicated indices from one face
101 void remove_duplicated_vertices(BaseImporter::VHandles& _indices)
102 {
103  BaseImporter::VHandles::iterator endIter = _indices.end();
104  for (BaseImporter::VHandles::iterator iter = _indices.begin(); iter != endIter; ++iter)
105  endIter = std::remove(iter+1, endIter, *(iter));
106 
107  _indices.erase(endIter,_indices.end());
108 }
109 
110 //-----------------------------------------------------------------------------
111 
112 _OBJReader_::
113 _OBJReader_()
114 {
115  IOManager().register_module(this);
116 }
117 
118 
119 //-----------------------------------------------------------------------------
120 
121 
122 bool
124 read(const std::string& _filename, BaseImporter& _bi, Options& _opt)
125 {
126  std::fstream in( _filename.c_str(), std::ios_base::in );
127 
128  if (!in.is_open() || !in.good())
129  {
130  omerr() << "[OBJReader] : cannot not open file "
131  << _filename
132  << std::endl;
133  return false;
134  }
135 
136  {
137 #if defined(WIN32)
138  std::string::size_type dot_pos = _filename.find_last_of("\\/");
139 #else
140  std::string::size_type dot_pos = _filename.rfind("/");
141 #endif
142  path_ = (dot_pos == std::string::npos)
143  ? "./"
144  : std::string(_filename.substr(0,dot_pos+1));
145  }
146 
147  bool result = read(in, _bi, _opt);
148 
149  in.close();
150  return result;
151 }
152 
153 //-----------------------------------------------------------------------------
154 
155 bool
156 _OBJReader_::
157 read_material(std::fstream& _in)
158 {
159  std::string line;
160  std::string keyWrd;
161  std::string textureName;
162 
163  std::stringstream stream;
164 
165  std::string key;
166  Material mat;
167  float f1,f2,f3;
168  bool indef = false;
169  int textureId = 1;
170 
171 
172  materials_.clear();
173  mat.cleanup();
174 
175  while( _in && !_in.eof() )
176  {
177  std::getline(_in,line);
178  if ( _in.bad() ){
179  omerr() << " Warning! Could not read file properly!\n";
180  return false;
181  }
182 
183  if ( line.empty() )
184  continue;
185 
186  stream.str(line);
187  stream.clear();
188 
189  stream >> keyWrd;
190 
191  if( ( isspace(line[0]) && line[0] != '\t' ) || line[0] == '#' )
192  {
193  if (indef && !key.empty() && mat.is_valid())
194  {
195  materials_[key] = mat;
196  mat.cleanup();
197  }
198  }
199 
200  else if (keyWrd == "newmtl") // begin new material definition
201  {
202  stream >> key;
203  indef = true;
204  }
205 
206  else if (keyWrd == "Kd") // diffuse color
207  {
208  stream >> f1; stream >> f2; stream >> f3;
209 
210  if( !stream.fail() )
211  mat.set_Kd(f1,f2,f3);
212  }
213 
214  else if (keyWrd == "Ka") // ambient color
215  {
216  stream >> f1; stream >> f2; stream >> f3;
217 
218  if( !stream.fail() )
219  mat.set_Ka(f1,f2,f3);
220  }
221 
222  else if (keyWrd == "Ks") // specular color
223  {
224  stream >> f1; stream >> f2; stream >> f3;
225 
226  if( !stream.fail() )
227  mat.set_Ks(f1,f2,f3);
228  }
229 #if 0
230  else if (keyWrd == "illum") // diffuse/specular shading model
231  {
232  ; // just skip this
233  }
234 
235  else if (keyWrd == "Ns") // Shininess [0..200]
236  {
237  ; // just skip this
238  }
239 
240  else if (keyWrd == "map_") // map images
241  {
242  // map_Ks, specular map
243  // map_Ka, ambient map
244  // map_Bump, bump map
245  // map_d, opacity map
246  ; // just skip this
247  }
248 #endif
249  else if (keyWrd == "map_Kd" ) {
250  // Get the rest of the line, removing leading or trailing spaces
251  // This will define the filename of the texture
252  std::getline(stream,textureName);
253  trimString(textureName);
254  if ( ! textureName.empty() )
255  mat.set_map_Kd( textureName, textureId++ );
256  }
257  else if (keyWrd == "Tr") // transparency value
258  {
259  stream >> f1;
260 
261  if( !stream.fail() )
262  mat.set_Tr(f1);
263  }
264  else if (keyWrd == "d") // transparency value
265  {
266  stream >> f1;
267 
268  if( !stream.fail() )
269  mat.set_Tr(f1);
270  }
271 
272  if ( _in && indef && mat.is_valid() && !key.empty())
273  materials_[key] = mat;
274  }
275  return true;
276 }
277 //-----------------------------------------------------------------------------
278 
279 bool
280 _OBJReader_::
281 read_vertices(std::istream& _in, BaseImporter& _bi, Options& _opt,
282  std::vector<Vec3f> & normals,
283  std::vector<Vec3f> & colors,
284  std::vector<Vec3f> & texcoords3d,
285  std::vector<Vec2f> & texcoords,
286  std::vector<VertexHandle> & vertexHandles,
287  Options & fileOptions)
288 {
289  float x, y, z, u, v, w;
290  float r, g, b;
291 
292  std::string line;
293  std::string keyWrd;
294 
295  std::stringstream stream;
296 
297 
298  // Options supplied by the user
299  const Options & userOptions = _opt;
300 
301  while( _in && !_in.eof() )
302  {
303  std::getline(_in,line);
304  if ( _in.bad() ){
305  omerr() << " Warning! Could not read file properly!\n";
306  return false;
307  }
308 
309  // Trim Both leading and trailing spaces
310  trimString(line);
311 
312  // comment
313  if ( line.size() == 0 || line[0] == '#' || isspace(line[0]) ) {
314  continue;
315  }
316 
317  stream.str(line);
318  stream.clear();
319 
320  stream >> keyWrd;
321 
322  // vertex
323  if (keyWrd == "v")
324  {
325  stream >> x; stream >> y; stream >> z;
326 
327  if ( !stream.fail() )
328  {
329  vertexHandles.push_back(_bi.add_vertex(OpenMesh::Vec3f(x,y,z)));
330  stream >> r; stream >> g; stream >> b;
331 
332  if ( !stream.fail() )
333  {
334  if ( userOptions.vertex_has_color() ) {
335  fileOptions += Options::VertexColor;
336  colors.push_back(OpenMesh::Vec3f(r,g,b));
337  }
338  }
339  }
340  }
341 
342  // texture coord
343  else if (keyWrd == "vt")
344  {
345  stream >> u; stream >> v;
346 
347  if ( !stream.fail() ){
348 
349  if ( userOptions.vertex_has_texcoord() || userOptions.face_has_texcoord() ) {
350  texcoords.push_back(OpenMesh::Vec2f(u, v));
351 
352  // Can be used for both!
353  fileOptions += Options::VertexTexCoord;
354  fileOptions += Options::FaceTexCoord;
355 
356  // try to read the w component as it is optional
357  stream >> w;
358  if ( !stream.fail() )
359  texcoords3d.push_back(OpenMesh::Vec3f(u, v, w));
360 
361  }
362 
363  }else{
364  omerr() << "Only single 2D or 3D texture coordinate per vertex"
365  << "allowed!" << std::endl;
366  return false;
367  }
368  }
369 
370  // color per vertex
371  else if (keyWrd == "vc")
372  {
373  stream >> r; stream >> g; stream >> b;
374 
375  if ( !stream.fail() ){
376  if ( userOptions.vertex_has_color() ) {
377  colors.push_back(OpenMesh::Vec3f(r,g,b));
378  fileOptions += Options::VertexColor;
379  }
380  }
381  }
382 
383  // normal
384  else if (keyWrd == "vn")
385  {
386  stream >> x; stream >> y; stream >> z;
387 
388  if ( !stream.fail() ) {
389  if (userOptions.vertex_has_normal() ){
390  normals.push_back(OpenMesh::Vec3f(x,y,z));
391  fileOptions += Options::VertexNormal;
392  }
393  }
394  }
395  }
396 
397  return true;
398 }
399 
400 //-----------------------------------------------------------------------------
401 
402 bool
404 read(std::istream& _in, BaseImporter& _bi, Options& _opt)
405 {
406  std::string line;
407  std::string keyWrd;
408 
409  std::vector<Vec3f> normals;
410  std::vector<Vec3f> colors;
411  std::vector<Vec3f> texcoords3d;
412  std::vector<Vec2f> texcoords;
413  std::vector<VertexHandle> vertexHandles;
414 
415  BaseImporter::VHandles vhandles;
416  std::vector<Vec3f> face_texcoords3d;
417  std::vector<Vec2f> face_texcoords;
418 
419  std::string matname;
420 
421  std::stringstream stream, lineData, tmp;
422 
423 
424  // Options supplied by the user
425  Options userOptions = _opt;
426 
427  // Options collected via file parsing
428  Options fileOptions;
429 
430  // pass 1: read vertices
431  if ( !read_vertices(_in, _bi, _opt,
432  normals, colors, texcoords3d, texcoords,
433  vertexHandles, fileOptions) ){
434  return false;
435  }
436 
437  // reset stream for second pass
438  _in.clear();
439  _in.seekg(0, std::ios::beg);
440 
441  int nCurrentPositions = 0,
442  nCurrentTexcoords = 0,
443  nCurrentNormals = 0;
444 
445  // pass 2: read faces
446  while( _in && !_in.eof() )
447  {
448  std::getline(_in,line);
449  if ( _in.bad() ){
450  omerr() << " Warning! Could not read file properly!\n";
451  return false;
452  }
453 
454  // Trim Both leading and trailing spaces
455  trimString(line);
456 
457  // comment
458  if ( line.size() == 0 || line[0] == '#' || isspace(line[0]) ) {
459  continue;
460  }
461 
462  stream.str(line);
463  stream.clear();
464 
465  stream >> keyWrd;
466 
467  // material file
468  if (keyWrd == "mtllib")
469  {
470  std::string matFile;
471 
472  // Get the rest of the line, removing leading or trailing spaces
473  // This will define the filename of the texture
474  std::getline(stream,matFile);
475  trimString(matFile);
476 
477  matFile = path_ + matFile;
478 
479  //omlog() << "Load material file " << matFile << std::endl;
480 
481  std::fstream matStream( matFile.c_str(), std::ios_base::in );
482 
483  if ( matStream ){
484 
485  if ( !read_material( matStream ) )
486  omerr() << " Warning! Could not read file properly!\n";
487  matStream.close();
488 
489  }else
490  omerr() << " Warning! Material file '" << matFile << "' not found!\n";
491 
492  //omlog() << " " << materials_.size() << " materials loaded.\n";
493 
494  for ( MaterialList::iterator material = materials_.begin(); material != materials_.end(); ++material )
495  {
496  // Save the texture information in a property
497  if ( (*material).second.has_map_Kd() )
498  _bi.add_texture_information( (*material).second.map_Kd_index() , (*material).second.map_Kd() );
499  }
500 
501  }
502 
503  // usemtl
504  else if (keyWrd == "usemtl")
505  {
506  stream >> matname;
507  if (materials_.find(matname)==materials_.end())
508  {
509  omerr() << "Warning! Material '" << matname
510  << "' not defined in material file.\n";
511  matname="";
512  }
513  }
514 
515  // track current number of parsed vertex attributes,
516  // to allow for OBJs negative indices
517  else if (keyWrd == "v")
518  {
519  ++nCurrentPositions;
520  }
521  else if (keyWrd == "vt")
522  {
523  ++nCurrentTexcoords;
524  }
525  else if (keyWrd == "vn")
526  {
527  ++nCurrentNormals;
528  }
529 
530  // faces
531  else if (keyWrd == "f")
532  {
533  int component(0), nV(0);
534  int value;
535 
536  vhandles.clear();
537  face_texcoords.clear();
538  face_texcoords3d.clear();
539 
540  // read full line after detecting a face
541  std::string faceLine;
542  std::getline(stream,faceLine);
543  lineData.str( faceLine );
544  lineData.clear();
545 
546  FaceHandle fh;
547  BaseImporter::VHandles faceVertices;
548 
549  // work on the line until nothing left to read
550  while ( !lineData.eof() )
551  {
552  // read one block from the line ( vertex/texCoord/normal )
553  std::string vertex;
554  lineData >> vertex;
555 
556  do{
557 
558  //get the component (vertex/texCoord/normal)
559  size_t found=vertex.find("/");
560 
561  // parts are seperated by '/' So if no '/' found its the last component
562  if( found != std::string::npos ){
563 
564  // read the index value
565  tmp.str( vertex.substr(0,found) );
566  tmp.clear();
567 
568  // If we get an empty string this property is undefined in the file
569  if ( vertex.substr(0,found).empty() ) {
570  // Switch to next field
571  vertex = vertex.substr(found+1);
572 
573  // Now we are at the next component
574  ++component;
575 
576  // Skip further processing of this component
577  continue;
578  }
579 
580  // Read current value
581  tmp >> value;
582 
583  // remove the read part from the string
584  vertex = vertex.substr(found+1);
585 
586  } else {
587 
588  // last component of the vertex, read it.
589  tmp.str( vertex );
590  tmp.clear();
591  tmp >> value;
592 
593  // Clear vertex after finished reading the line
594  vertex="";
595 
596  // Nothing to read here ( garbage at end of line )
597  if ( tmp.fail() ) {
598  continue;
599  }
600  }
601 
602  // store the component ( each component is referenced by the index here! )
603  switch (component)
604  {
605  case 0: // vertex
606  if ( value < 0 ) {
607  // Calculation of index :
608  // -1 is the last vertex in the list
609  // As obj counts from 1 and not zero add +1
610  value = nCurrentPositions + value + 1;
611  }
612  // Obj counts from 1 and not zero .. array counts from zero therefore -1
613  vhandles.push_back(VertexHandle(value-1));
614  faceVertices.push_back(VertexHandle(value-1));
615  if (fileOptions.vertex_has_color()) {
616  if ((unsigned int)(value - 1) < colors.size()) {
617  _bi.set_color(vhandles.back(), colors[value - 1]);
618  }
619  else {
620  omerr() << "Error setting vertex color" << std::endl;
621  }
622  }
623  break;
624 
625  case 1: // texture coord
626  if ( value < 0 ) {
627  // Calculation of index :
628  // -1 is the last vertex in the list
629  // As obj counts from 1 and not zero add +1
630  value = nCurrentTexcoords + value + 1;
631  }
632  assert(!vhandles.empty());
633 
634 
635  if ( fileOptions.vertex_has_texcoord() && userOptions.vertex_has_texcoord() ) {
636 
637  if (!texcoords.empty() && (unsigned int) (value - 1) < texcoords.size()) {
638  // Obj counts from 1 and not zero .. array counts from zero therefore -1
639  _bi.set_texcoord(vhandles.back(), texcoords[value - 1]);
640  if(!texcoords3d.empty() && (unsigned int) (value -1) < texcoords3d.size())
641  _bi.set_texcoord(vhandles.back(), texcoords3d[value - 1]);
642  } else {
643  omerr() << "Error setting Texture coordinates" << std::endl;
644  }
645 
646  }
647 
648  if (fileOptions.face_has_texcoord() && userOptions.face_has_texcoord() ) {
649 
650  if (!texcoords.empty() && (unsigned int) (value - 1) < texcoords.size()) {
651  face_texcoords.push_back( texcoords[value-1] );
652  if(!texcoords3d.empty() && (unsigned int) (value -1) < texcoords3d.size())
653  face_texcoords3d.push_back( texcoords3d[value-1] );
654  } else {
655  omerr() << "Error setting Texture coordinates" << std::endl;
656  }
657  }
658 
659 
660  break;
661 
662  case 2: // normal
663  if ( value < 0 ) {
664  // Calculation of index :
665  // -1 is the last vertex in the list
666  // As obj counts from 1 and not zero add +1
667  value = nCurrentNormals + value + 1;
668  }
669 
670  // Obj counts from 1 and not zero .. array counts from zero therefore -1
671  if (fileOptions.vertex_has_normal() ) {
672  assert(!vhandles.empty());
673  if ((unsigned int)(value - 1) < normals.size()) {
674  _bi.set_normal(vhandles.back(), normals[value - 1]);
675  }
676  else {
677  omerr() << "Error setting vertex normal" << std::endl;
678  }
679  }
680  break;
681  }
682 
683  // Prepare for reading next component
684  ++component;
685 
686  // Read until line does not contain any other info
687  } while ( !vertex.empty() );
688 
689  component = 0;
690  nV++;
691 
692  }
693 
694  // note that add_face can possibly triangulate the faces, which is why we have to
695  // store the current number of faces first
696  size_t n_faces = _bi.n_faces();
697  remove_duplicated_vertices(faceVertices);
698 
699  //A minimum of three vertices are required.
700  if (faceVertices.size() > 2)
701  fh = _bi.add_face(faceVertices);
702 
703  if (!vhandles.empty() && fh.is_valid() )
704  {
705  _bi.add_face_texcoords(fh, vhandles[0], face_texcoords);
706  _bi.add_face_texcoords(fh, vhandles[0], face_texcoords3d);
707  }
708 
709  if ( !matname.empty() )
710  {
711  std::vector<FaceHandle> newfaces;
712 
713  for( size_t i=0; i < _bi.n_faces()-n_faces; ++i )
714  newfaces.push_back(FaceHandle(int(n_faces+i)));
715 
716  Material& mat = materials_[matname];
717 
718  if ( mat.has_Kd() ) {
719  Vec3uc fc = color_cast<Vec3uc, Vec3f>(mat.Kd());
720 
721  if ( userOptions.face_has_color()) {
722 
723  for (std::vector<FaceHandle>::iterator it = newfaces.begin(); it != newfaces.end(); ++it)
724  _bi.set_color(*it, fc);
725 
726  fileOptions += Options::FaceColor;
727  }
728  }
729 
730  // Set the texture index in the face index property
731  if ( mat.has_map_Kd() ) {
732 
733  if (userOptions.face_has_texcoord()) {
734 
735  for (std::vector<FaceHandle>::iterator it = newfaces.begin(); it != newfaces.end(); ++it)
736  _bi.set_face_texindex(*it, mat.map_Kd_index());
737 
738  fileOptions += Options::FaceTexCoord;
739 
740  }
741 
742  } else {
743 
744  // If we don't have the info, set it to no texture
745  if (userOptions.face_has_texcoord()) {
746 
747  for (std::vector<FaceHandle>::iterator it = newfaces.begin(); it != newfaces.end(); ++it)
748  _bi.set_face_texindex(*it, 0);
749 
750  }
751  }
752 
753  } else {
754  std::vector<FaceHandle> newfaces;
755 
756  for( size_t i=0; i < _bi.n_faces()-n_faces; ++i )
757  newfaces.push_back(FaceHandle(int(n_faces+i)));
758 
759  // Set the texture index to zero as we don't have any information
760  if ( userOptions.face_has_texcoord() )
761  for (std::vector<FaceHandle>::iterator it = newfaces.begin(); it != newfaces.end(); ++it)
762  _bi.set_face_texindex(*it, 0);
763  }
764 
765  }
766 
767  }
768 
769  // If we do not have any faces,
770  // assume this is a point cloud and read the normals and colors directly
771  if (_bi.n_faces() == 0)
772  {
773  int i = 0;
774  // add normal per vertex
775 
776  if (normals.size() == _bi.n_vertices()) {
777  if ( fileOptions.vertex_has_normal() && userOptions.vertex_has_normal() ) {
778  for (std::vector<VertexHandle>::iterator it = vertexHandles.begin(); it != vertexHandles.end(); ++it, i++)
779  _bi.set_normal(*it, normals[i]);
780  }
781  }
782 
783  // add color per vertex
784  i = 0;
785  if (colors.size() >= _bi.n_vertices())
786  if (fileOptions.vertex_has_color() && userOptions.vertex_has_color()) {
787  for (std::vector<VertexHandle>::iterator it = vertexHandles.begin(); it != vertexHandles.end(); ++it, i++)
788  _bi.set_color(*it, colors[i]);
789  }
790 
791  }
792 
793  // Return, what we actually read
794  _opt = fileOptions;
795 
796  return true;
797 }
798 
799 
800 //=============================================================================
801 } // namespace IO
802 } // namespace OpenMesh
803 //=============================================================================
bool read(const std::string &_filename, BaseImporter &_bi, Options &_opt) override
Definition: OBJReader.cc:124
Handle for a face entity.
Definition: Handles.hh:141
Has (r) / store (w) vertex colors.
Definition: Options.hh:105
void clear(void)
Clear all bits.
Definition: Options.hh:147
Has (r) / store (w) face colors.
Definition: Options.hh:109
Handle for a vertex entity.
Definition: Handles.hh:120
bool is_valid() const
The handle is valid iff the index is not negative.
Definition: Handles.hh:72
Has (r) / store (w) face texture coordinates.
Definition: Options.hh:110
bool register_module(BaseReader *_bl)
Definition: IOManager.hh:217
Set options for reader/writer modules.
Definition: Options.hh:90
VectorT< unsigned char, 3 > Vec3uc
Definition: Vector11T.hh:840
Has (r) / store (w) vertex normals.
Definition: Options.hh:104
_IOManager_ & IOManager()
Definition: IOManager.cc:72
Has (r) / store (w) texture coordinates.
Definition: Options.hh:106