MEPP2 Project
ObjFileReader.h
Go to the documentation of this file.
1 // Copyright (c) 2012-2019 University of Lyon and CNRS (France).
2 // All rights reserved.
3 //
4 // This file is part of MEPP2; you can redistribute it and/or modify
5 // it under the terms of the GNU Lesser General Public License as
6 // published by the Free Software Foundation; either version 3 of
7 // the License, or (at your option) any later version.
8 //
9 // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
10 // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11 #pragma once
12 
13 /*
14  * OBJ ref:
15  * - http://www.martinreddy.net/gfx/3d/OBJ.spec
16  * - https://en.wikipedia.org/wiki/Wavefront_.obj_file
17  * - http://paulbourke.net/dataformats/obj
18  * - http://paulbourke.net/dataformats/mtl
19  */
20 
21 #include <iostream>
22 #include <vector>
23 #include <cassert>
24 #include <cstdio>
25 #include <string>
26 #include <exception>
27 
30 #include "FEVV/Types/Material.h"
31 
32 
33 namespace FEVV {
34 namespace IO {
35 
36 
37 using namespace StrUtils;
38 using namespace FileUtils;
39 
43 template< typename MaterialType >
44 void
45 dbg_display_materials(const std::vector< MaterialType > &materials)
46 {
47  std::cout << "materials:" << std::endl;
48  for(auto mat : materials)
49  {
50  std::cout << " " << mat.name << std::endl;
51  std::cout << "\tdiffuse/albedo: " << mat.diffuse_texture_filename
52  << std::endl;
53  std::cout << "\tspecular: " << mat.specular_texture_filename << std::endl;
54  std::cout << "\ttransparency: " << mat.transparency_texture_filename
55  << std::endl;
56  std::cout << "\tbump/normal: " << mat.normal_map_filename << std::endl;
57  std::cout << "\tmetallic: " << mat.metallic_map_filename << std::endl;
58  std::cout << "\troughness: " << mat.roughness_map_filename << std::endl;
59  std::cout << "\temissive: " << mat.emissive_texture_filename << std::endl;
60  std::cout << "\tambient/ambient occlusion: " << mat.ambient_texture_filename
61  << std::endl;
62  }
63 }
64 
65 
69 template< typename MaterialType >
70 void
71 read_mtl_file(const std::string &mtl_file_name,
72  std::vector< MaterialType > &materials)
73 {
74  // DBG std::cout << "mtl_file_name = " << mtlFileName << std::endl;
75 
76  std::ifstream file(mtl_file_name);
77  if(!file.is_open())
78  {
79  std::cout << "Failed to open MTL file '" << mtl_file_name << "'"
80  << std::endl;
81  return;
82  }
83 
84  const std::string parent_directory = get_parent_directory(mtl_file_name);
85 
86  // read materials
87  Types::Material new_material;
88 
89  std::string word;
90 
91  while(!file.eof())
92  {
93  file >> word;
94  // DBG std::cout << "word=" << word << std::endl;
95 
96  // RM: import all material's features (coefficients & textures names)
97  if(word == "newmtl")
98  {
99  if(!new_material.name.empty())
100  {
101  materials.push_back(new_material);
102  new_material = Types::Material();
103  }
104 
105  file >> new_material.name;
106  }
107  else if(word[0] == 'm') // Texture [map_*]
108  {
109  // we suppose there is only one 'map_Kd' per material
110  std::string texture_filename;
111  file >> texture_filename;
112  texture_filename.insert(0, parent_directory + "/");
113 
114  if(word[4] == 'K') // Standard textures [map_K*]
115  {
116  if(word[5] ==
117  'a') // Ambient texture [map_Ka]; used for PBR as ambient occlusion
118  {
119  new_material.ambient_texture_filename = texture_filename;
120  }
121  else if(word[5] ==
122  'd') // Diffuse texture [map_Kd]; used for PBR as albedo
123  {
124  new_material.diffuse_texture_filename = texture_filename;
125  }
126  else if(word[5] == 's') // Specular texture [map_Ks]
127  {
128  new_material.specular_texture_filename = texture_filename;
129  }
130  else if(word[5] == 'e') // Emissive texture [map_Ke]
131  {
132  new_material.emissive_texture_filename = texture_filename;
133  }
134  }
135  else if(word[4] == 'P') // PBR texture [map_P*]
136  {
137  if(word[5] == 'm') // Metallic map [map_Pm]
138  {
139  new_material.metallic_map_filename = texture_filename;
140  }
141  else if(word[5] == 'r') // Roughness map [map_Pr]
142  {
143  new_material.roughness_map_filename = texture_filename;
144  }
145 
147  }
148  else if(word[4] == 'd') // Transparency texture [map_d]
149  {
150  new_material.transparency_texture_filename = texture_filename;
151  }
152  else if(word[4] == 'b') // Bump or normal map [map_bump]
153  {
154  new_material.normal_map_filename = texture_filename;
155 
156  // RM: if normal map filename contains "normal" or "nrm", consider it a
157  // normal map instead of a bump map
158  const std::string lower_tex_filename =
159  boost::algorithm::to_lower_copy(texture_filename);
160  if(lower_tex_filename.find("normal") != std::string::npos ||
161  lower_tex_filename.find("nrm") != std::string::npos)
162  new_material.has_normal_map = true;
163  }
164  }
165  else if(word[0] == 'K')
166  {
167  std::string token_r, token_g, token_b;
168  file >> token_r >> token_g >> token_b;
169 
170  if(word[1] == 'a') // Ambient color [Ka]
171  {
172  convert(token_r, new_material.ambient_red_component);
173  convert(token_g, new_material.ambient_green_component);
174  convert(token_b, new_material.ambient_blue_component);
175  }
176  else if(word[1] == 'd') // Diffuse color [Kd]; used for PBR as albedo
177  {
178  convert(token_r, new_material.diffuse_red_component);
179  convert(token_g, new_material.diffuse_green_component);
180  convert(token_b, new_material.diffuse_blue_component);
181  }
182  else if(word[1] == 's') // Specular color [Ks]
183  {
184  convert(token_r, new_material.specular_red_component);
185  convert(token_g, new_material.specular_green_component);
186  convert(token_b, new_material.specular_blue_component);
187  }
188  else if(word[1] == 'e') // Emissive color [Ke]
189  {
190  convert(token_r, new_material.emissive_red_component);
191  convert(token_g, new_material.emissive_green_component);
192  convert(token_b, new_material.emissive_blue_component);
193  }
194  }
195  else if(word[0] == 'P') // PBR factor [P*]
196  {
197  std::string factor;
198  file >> factor;
199 
200  if(word[1] == 'm') // Metallic factor [Pm]
201  {
202  convert(factor, new_material.metallic_factor);
203  }
204  else if(word[1] == 'r') // Roughness factor [Pr]
205  {
206  convert(factor, new_material.roughness_factor);
207  }
208 
210  }
211  else if(word[0] == 'T')
212  {
213  std::string value;
214  file >> value;
215 
216  if(word[1] == 'r') // Transparency (reversed, 1 - value) [Tr]
217  {
218  convert(value, new_material.transparency);
219  new_material.transparency = 1.0 - new_material.transparency;
220  }
221  /*else if( word[1] == 'i' ) // Transmission filter [Ti]
222  {
223 
224  }*/
225  }
226  else if(word[0] == 'b') // Bump or normal map [bump]
227  {
228  std::string texture_filename;
229  file >> texture_filename;
230  texture_filename.insert(0, parent_directory + "/");
231 
232  new_material.normal_map_filename = texture_filename;
233 
234  // RM: if normal map filename contains "normal" or "nrm", consider it a
235  // normal map instead of a bump map
236  const std::string lower_tex_filename =
237  boost::algorithm::to_lower_copy(texture_filename);
238  if(lower_tex_filename.find("normal") != std::string::npos ||
239  lower_tex_filename.find("nrm") != std::string::npos)
240  new_material.has_normal_map = true;
241  }
242  else if(word == "norm") // Normal map [norm]
243  {
244  std::string texture_filename;
245  file >> texture_filename;
246  texture_filename.insert(0, parent_directory + "/");
247 
248  new_material.normal_map_filename = texture_filename;
249  new_material.has_normal_map = true;
250  }
251  else if(word[0] == 'd') // Transparency [d]
252  {
253  std::string value;
254  file >> value;
255 
256  convert(value, new_material.transparency);
257  }
258  else if(word[0] == 'i') // Illumination [i]
259  {
260  // empty canvas, not yet supported
261 
262  std::string value;
263  file >> value;
264 
265  switch(std::stoul(value))
266  {
267  case 0: // Color ON, ambient OFF
268  break;
269 
270  case 1: // Color ON, ambient ON
271  break;
272 
273  case 2: // Highlight ON
274  break;
275 
276  case 3: // Reflection ON, raytrace ON
277  break;
278 
279  case 4: // Transparency: glass ON; reflection: raytrace ON
280  break;
281 
282  case 5: // Reflection: Fresnel ON, raytrace ON
283  break;
284 
285  case 6: // Transparency: refraction ON; reflection: Fresnel OFF, raytrace
286  // ON
287  break;
288 
289  case 7: // Transparency: refraction ON; reflection: Fresnel ON, raytrace
290  // ON
291  break;
292 
293  case 8: // Reflection ON, raytrace OFF
294  break;
295 
296  case 9: // Transparency: glass ON; reflection: raytrace OFF
297  break;
298 
299  case 10: // Casts shadows onto invisible surfaces
300  break;
301 
302  default:
303  break;
304  }
305  }
306 
307  std::getline(file, word); // Recovering the rest of the line, to avoid the
308  // same line being taken twice
309  }
310 
311  materials.push_back(new_material);
312 }
313 
317 template< typename CoordType,
318  typename CoordNType,
319  typename CoordTType,
320  typename CoordCType,
321  typename IndexType,
322  typename MaterialType >
323 void
324 read_obj_file(const std::string &file_path,
325  std::vector< std::vector< CoordType > > &points_coords,
326  std::vector< std::vector< CoordNType > > &normals_coords,
327  std::vector< std::vector< CoordTType > > &texture_coords,
328  std::vector< std::vector< CoordCType > > &vertex_color_coords,
329  std::vector< std::vector< IndexType > > &face_indices,
330  std::vector< std::vector< IndexType > > &texture_face_indices,
331  std::vector< std::vector< IndexType > > &normal_face_indices,
332  std::vector< MaterialType > &materials, // list of materials
333  std::vector< IndexType > &face_material) // material of each face
334 {
335  points_coords.clear();
336  normals_coords.clear();
337  face_indices.clear();
338  texture_face_indices.clear();
339 
340  unsigned int cpt_l = count_file_lines(file_path);
341  points_coords.reserve((size_t)(cpt_l * 0.4f));
342  // TO DO find exactly the nb of points
343  normals_coords.reserve((size_t)(cpt_l * 0.4f));
344  face_indices.reserve((size_t)(cpt_l * 0.6f));
345  // TO DO find exactly the nb of faces
346 
347  std::ifstream file(file_path);
348 
349  if(! file.is_open())
350  {
351  throw std::runtime_error(
352  "Reader::read_obj_file -> input file failed to open.");
353  }
354 
355  std::string line_str, word;
356  std::istringstream line_ss, word_ss;
357  char delim;
358 
359  bool usemtl_found = false;
360  IndexType current_material_id = -1;
361 
362  CoordType coord;
363  CoordNType n_coord;
364  CoordTType t_coord;
365  CoordCType c_coord;
366  int64_t v_ind, t_ind, n_ind;
367 
368  // declare containers outside main loop to avoid creation/destruction
369  // at each iteration and save some time
370  std::vector< CoordNType > normal;
371  std::vector< CoordTType > tex_coord;
372  std::vector< CoordType > point;
373  std::vector< CoordCType > color;
374  std::vector< IndexType > face_points, face_textures, face_normals;
375 
376 
377  while(getline_skip_comment(file, line_str, line_ss))
378  {
379  line_ss >> word;
380 
381  if(word == "o") // MESH NAME
382  {
383  // TODO : find a place to keep the mesh name
384  }
385  else if(word == "mtllib")
386  {
387  // read mtl file name
388  line_ss >> word;
389 
390  // append mtl file name to obj file path
391  std::string str_parent_directory = get_parent_directory(file_path);
392  if(str_parent_directory.empty())
393  str_parent_directory = ".";
394  std::string mtl_file_name = str_parent_directory + "/" + word;
395 
396  // search texture file name in mtl file
397  read_mtl_file(mtl_file_name, materials);
398  /*//TODO-elo-dbg*/ dbg_display_materials(materials);
399  }
400  else if(word == "vn") // NORMAL POINT
401  {
402  normal.clear();
403 
404  for(unsigned int i = 1; i < 4; ++i)
405  {
406  line_ss >> n_coord;
407  normal.push_back(n_coord);
408  }
409  normals_coords.push_back(normal);
410  }
411  else if(word == "vt") // TEXTURE POINT
412  {
413  tex_coord.clear();
414 
415  for(unsigned int i = 1; i < 3; ++i)
416  {
417  line_ss >> t_coord;
418  tex_coord.push_back(t_coord);
419  }
420  texture_coords.push_back(tex_coord);
421  }
422  else if(word == "v") // POINT
423  {
424  // supported line format:
425  // - "v x y z"
426  // - "v x y z r g b"
427  // homogeneous point (x, y, z, w) not supported
428 
429  point.clear();
430  color.clear();
431 
432  // read point coordinates
433  for(unsigned int i = 0; i < 3; ++i)
434  {
435  line_ss >> coord;
436  point.push_back(coord);
437  }
438 
439  if(line_ss.fail())
440  {
441  // remove DOS end of line extra character
442  if(line_str.back() == '\r')
443  line_str.pop_back();
444 
445  throw std::runtime_error(
446  "Reader::read_obj_file -> unsupported line format at line "
447  "'" + line_str + "'. "
448  "Supported format is 'v x y z [r g b]'.");
449  }
450 
451  // read point color if present
452  for(unsigned int i = 0; i < 3; ++i)
453  {
454  line_ss >> c_coord;
455  color.push_back(c_coord);
456  }
457 
458  if(line_ss.fail())
459  {
460  // invalid color
461  color.clear();
462  }
463 
464  points_coords.push_back(point);
465  vertex_color_coords.push_back(color);
466  }
467  else if(word == "f") // FACE
468  {
469  // supported line format:
470  // - minimal aka vertex indices only
471  // "f v1 v2 v3 ...."
472  // - with vertex texture
473  // "f v1/vt1 v2/vt2 v3/vt3 ..."
474  // - with vertex texture and vertex normal
475  // "f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ..."
476  // - with vertex normal
477  // "f v1//vn1 v2//vn2 v3//vn3 ..."
478 
479  face_points.clear();
480  face_textures.clear();
481  face_normals.clear();
482 
483  // parse each "v1/vt1/vn1" (or variant) of the face line
484  while(line_ss >> word)
485  {
486  word_ss.clear();
487  word_ss.str(word);
488 
489  // read vertex indice
490  word_ss >> v_ind;
491  face_points.push_back(static_cast< IndexType >(
492  v_ind < 0 ? v_ind + points_coords.size() : v_ind - 1));
493  // the indices needs to start to 0, which is
494  // not the case in obj format (starts to 1)
495 
496  // read vertex texture indice if any
497  if((word_ss >> delim) && (word_ss >> t_ind))
498  {
499  face_textures.push_back(static_cast< IndexType >(
500  t_ind < 0 ? t_ind + texture_coords.size() : t_ind - 1));
501  }
502 
503  // clear stream error state in case there is no vertex texture
504  // and a vertex normal aka "v1//vn1"
505  word_ss.clear();
506 
507  // read vertex normal
508  if((word_ss >> delim) && (word_ss >> n_ind))
509  {
510  face_normals.push_back(static_cast< IndexType >(
511  n_ind < 0 ? n_ind + normals_coords.size() : n_ind - 1));
512  }
513  }
514 
515  // ensure all or none vertices of the face have texture or normal
516  bool texture_ok =
517  face_textures.empty() ||
518  (face_textures.size() == face_points.size());
519  bool normal_ok =
520  face_normals.empty() ||
521  (face_normals.size() == face_points.size());
522  if(! (texture_ok && normal_ok))
523  {
524  // remove DOS end of line extra character
525  if(line_str.back() == '\r')
526  line_str.pop_back();
527 
528  throw std::runtime_error(
529  "Reader::read_obj_file -> unsupported line format at line "
530  "'" + line_str + "'. "
531  "Supported formats are: "
532  "'f v1 v2 v3 ....', "
533  "'f v1/vt1 v2/vt2 v3/vt3 ...', "
534  "'f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ...', "
535  "'f v1//vn1 v2//vn2 v3//vn3 ...'.");
536  }
537 
538  face_indices.push_back(face_points);
539  texture_face_indices.push_back(face_textures);
540  normal_face_indices.push_back(face_normals);
541  if(usemtl_found)
542  face_material.push_back(current_material_id);
543  }
544  else if(word == "usemtl") // MATERIAL
545  {
546  usemtl_found = true;
547  std::string mtl_name;
548  line_ss >> mtl_name;
549  current_material_id = -1;
550  // std::cout << "---------> mtl_name: " << mtl_name << std::endl;
551  for(IndexType i = 0; i < materials.size(); i++)
552  {
553  // std::cout << "-------------------> materials[i].name: " <<
554  // materials[i].name << std::endl;
555  if(materials[i].name == mtl_name)
556  {
557  current_material_id = i;
558  break;
559  }
560  }
561  }
562  }
563 
564  file.close();
565 }
566 
567 } // namespace IO
568 } // namespace FEVV
569 
FEVV::StrUtils::convert
void convert(const std::string &str, ConvertType &elem)
Definition: StringUtilities.hpp:81
FEVV::Types::Material::diffuse_blue_component
double diffuse_blue_component
Definition: Material.h:77
FEVV::Types::Material::ambient_texture_filename
std::string ambient_texture_filename
Definition: Material.h:95
FEVV::Types::Material::emissive_texture_filename
std::string emissive_texture_filename
Definition: Material.h:99
FEVV::Types::Material::emissive_red_component
double emissive_red_component
Definition: Material.h:83
FEVV::Types::Material::roughness_factor
double roughness_factor
Definition: Material.h:92
FEVV::Types::MaterialType
MaterialType
Definition: Material.h:38
FEVV::Types::Material::diffuse_texture_filename
std::string diffuse_texture_filename
Definition: Material.h:97
FEVV::Types::Material::diffuse_red_component
double diffuse_red_component
Definition: Material.h:75
FEVV::Types::Material::specular_blue_component
double specular_blue_component
Definition: Material.h:81
Material.h
FEVV::Types::Material::specular_green_component
double specular_green_component
Definition: Material.h:80
FEVV::FileUtils::count_file_lines
unsigned int count_file_lines(const std::string &file_name)
Definition: FileUtilities.hpp:163
FEVV::Types::Material::metallic_map_filename
std::string metallic_map_filename
Definition: Material.h:106
FEVV::IO::read_mtl_file
void read_mtl_file(const std::string &mtl_file_name, std::vector< MaterialType > &materials)
Definition: ObjFileReader.h:71
FEVV::Types::MaterialType::MATERIAL_TYPE_STANDARD
@ MATERIAL_TYPE_STANDARD
FEVV::Types::Material::transparency
double transparency
Definition: Material.h:87
FEVV
Interfaces for plugins These interfaces will be used for different plugins.
Definition: Assert.h:16
FEVV::Types::Material::name
std::string name
Definition: Material.h:67
FEVV::Types::Material::normal_map_filename
std::string normal_map_filename
Definition: Material.h:101
FEVV::face_material
@ face_material
Definition: properties.h:85
FEVV::IO::read_obj_file
void read_obj_file(const std::string &file_path, std::vector< std::vector< CoordType > > &points_coords, std::vector< std::vector< CoordNType > > &normals_coords, std::vector< std::vector< CoordTType > > &texture_coords, std::vector< std::vector< CoordCType > > &vertex_color_coords, std::vector< std::vector< IndexType > > &face_indices, std::vector< std::vector< IndexType > > &texture_face_indices, std::vector< std::vector< IndexType > > &normal_face_indices, std::vector< MaterialType > &materials, std::vector< IndexType > &face_material)
Definition: ObjFileReader.h:324
FEVV::Types::Material::emissive_green_component
double emissive_green_component
Definition: Material.h:84
StringUtilities.hpp
FEVV::IO::dbg_display_materials
void dbg_display_materials(const std::vector< MaterialType > &materials)
Definition: ObjFileReader.h:45
FEVV::Types::Material
Definition: Material.h:66
FEVV::Types::Material::type
MaterialType type
Definition: Material.h:68
FEVV::Types::Material::specular_red_component
double specular_red_component
Definition: Material.h:79
FEVV::Types::Material::transparency_texture_filename
std::string transparency_texture_filename
Definition: Material.h:100
FEVV::FileUtils::get_parent_directory
std::string get_parent_directory(const std::string &file_name)
Definition: FileUtilities.hpp:191
FEVV::Types::Material::roughness_map_filename
std::string roughness_map_filename
Definition: Material.h:107
FEVV::Types::Material::ambient_green_component
double ambient_green_component
Definition: Material.h:72
FEVV::Types::Material::specular_texture_filename
std::string specular_texture_filename
Definition: Material.h:98
FEVV::Types::Material::ambient_red_component
double ambient_red_component
Definition: Material.h:71
FEVV::Types::Material::diffuse_green_component
double diffuse_green_component
Definition: Material.h:76
FEVV::Types::Material::metallic_factor
double metallic_factor
Definition: Material.h:91
FileUtilities.hpp
FEVV::Types::Material::ambient_blue_component
double ambient_blue_component
Definition: Material.h:73
FEVV::Types::Material::emissive_blue_component
double emissive_blue_component
Definition: Material.h:85
FEVV::Types::Material::has_normal_map
bool has_normal_map
Definition: Material.h:110
FEVV::FileUtils::getline_skip_comment
bool getline_skip_comment(std::istream &input, std::string &line)
Definition: FileUtilities.hpp:230