1 /*
2  * Software License Agreement (BSD License)
3  *
4  *  Point Cloud Library (PCL) - www.pointclouds.org
5  *  Copyright (c) 2010, Willow Garage, Inc.
6  *  Copyright (c) 2013, Open Perception, Inc.
7  *
8  *  All rights reserved.
9  *
10  *  Redistribution and use in source and binary forms, with or without
11  *  modification, are permitted provided that the following conditions
12  *  are met:
13  *
14  *   * Redistributions of source code must retain the above copyright
15  *     notice, this list of conditions and the following disclaimer.
16  *   * Redistributions in binary form must reproduce the above
17  *     copyright notice, this list of conditions and the following
18  *     disclaimer in the documentation and/or other materials provided
19  *     with the distribution.
20  *   * Neither the name of Willow Garage, Inc. nor the names of its
21  *     contributors may be used to endorse or promote products derived
22  *     from this software without specific prior written permission.
23  *
24  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34  *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  *  POSSIBILITY OF SUCH DAMAGE.
36  *
37  */
38 #include <pcl/io/obj_io.h>
39 #include <fstream>
40 #include <pcl/common/io.h>
41 #include <pcl/console/time.h>
42 #include <boost/lexical_cast.hpp> // for lexical_cast
43 #include <boost/filesystem.hpp> // for exists
44 #include <boost/algorithm/string.hpp> // for split
45 
MTLReader()46 pcl::MTLReader::MTLReader ()
47 {
48   xyz_to_rgb_matrix_ << 2.3706743, -0.9000405, -0.4706338,
49                        -0.5138850,  1.4253036,  0.0885814,
50                         0.0052982, -0.0146949,  1.0093968;
51 }
52 
53 inline void
cie2rgb(const Eigen::Vector3f & xyz,pcl::TexMaterial::RGB & rgb) const54 pcl::MTLReader::cie2rgb (const Eigen::Vector3f &xyz, pcl::TexMaterial::RGB& rgb) const
55 {
56   Eigen::Vector3f rgb_vec = xyz_to_rgb_matrix_ * xyz;
57   rgb.r = rgb_vec[0]; rgb.g = rgb_vec[1]; rgb.b = rgb_vec[2];
58 }
59 
60 int
fillRGBfromXYZ(const std::vector<std::string> & split_line,pcl::TexMaterial::RGB & rgb)61 pcl::MTLReader::fillRGBfromXYZ (const std::vector<std::string>& split_line,
62                                 pcl::TexMaterial::RGB& rgb)
63 {
64   Eigen::Vector3f xyz;
65   if (split_line.size () == 5)
66   {
67     try
68     {
69       xyz[0] = boost::lexical_cast<float> (split_line[2]);
70       xyz[1] = boost::lexical_cast<float> (split_line[3]);
71       xyz[2] = boost::lexical_cast<float> (split_line[4]);
72     }
73     catch (boost::bad_lexical_cast &)
74     {
75       return (-1);
76     }
77   }
78   else
79     if (split_line.size () == 3)
80     {
81       try
82       {
83         xyz[0] = xyz[1] = xyz[2] = boost::lexical_cast<float> (split_line[2]);
84       }
85       catch (boost::bad_lexical_cast &)
86       {
87         return (-1);
88       }
89     }
90     else
91       return (-1);
92 
93   cie2rgb (xyz, rgb);
94   return (0);
95 }
96 
97 int
fillRGBfromRGB(const std::vector<std::string> & split_line,pcl::TexMaterial::RGB & rgb)98 pcl::MTLReader::fillRGBfromRGB (const std::vector<std::string>& split_line,
99                                 pcl::TexMaterial::RGB& rgb)
100 {
101   if (split_line.size () == 4)
102   {
103     try
104     {
105       rgb.r = boost::lexical_cast<float> (split_line[1]);
106       rgb.g = boost::lexical_cast<float> (split_line[2]);
107       rgb.b = boost::lexical_cast<float> (split_line[3]);
108     }
109     catch (boost::bad_lexical_cast &)
110     {
111       rgb.r = rgb.g = rgb.b = 0;
112       return (-1);
113     }
114   }
115   else
116     if (split_line.size () == 2)
117     {
118       try
119       {
120         rgb.r = rgb.g = rgb.b = boost::lexical_cast<float> (split_line[1]);
121       }
122       catch (boost::bad_lexical_cast &)
123       {
124         return (-1);
125       }
126     }
127     else
128       return (-1);
129 
130   return (0);
131 }
132 
133 std::vector<pcl::TexMaterial>::const_iterator
getMaterial(const std::string & material_name) const134 pcl::MTLReader::getMaterial (const std::string& material_name) const
135 {
136   std::vector<pcl::TexMaterial>::const_iterator mat_it = materials_.begin ();
137   for (; mat_it != materials_.end (); ++mat_it)
138     if (mat_it->tex_name == material_name)
139       break;
140   return (mat_it);
141 }
142 
143 int
read(const std::string & obj_file_name,const std::string & mtl_file_name)144 pcl::MTLReader::read (const std::string& obj_file_name,
145                       const std::string& mtl_file_name)
146 {
147   if (obj_file_name.empty() || !boost::filesystem::exists (obj_file_name))
148   {
149     PCL_ERROR ("[pcl::MTLReader::read] Could not find file '%s'!\n",
150                obj_file_name.c_str ());
151     return (-1);
152   }
153 
154   if (mtl_file_name.empty())
155   {
156     PCL_ERROR ("[pcl::MTLReader::read] MTL file name is empty!\n");
157     return (-1);
158   }
159 
160   boost::filesystem::path obj_file_path (obj_file_name.c_str ());
161   boost::filesystem::path mtl_file_path = obj_file_path.parent_path ();
162   mtl_file_path /=  mtl_file_name;
163   return (read (mtl_file_path.string ()));
164 }
165 
166 int
read(const std::string & mtl_file_path)167 pcl::MTLReader::read (const std::string& mtl_file_path)
168 {
169   if (mtl_file_path.empty() || !boost::filesystem::exists (mtl_file_path))
170   {
171     PCL_ERROR ("[pcl::MTLReader::read] Could not find file '%s'.\n", mtl_file_path.c_str ());
172     return (-1);
173   }
174 
175   std::ifstream mtl_file;
176   mtl_file.open (mtl_file_path.c_str (), std::ios::binary);
177   if (!mtl_file.is_open () || mtl_file.fail ())
178   {
179     PCL_ERROR ("[pcl::MTLReader::read] Could not open file '%s'! Error : %s\n",
180                mtl_file_path.c_str (), strerror(errno));
181     mtl_file.close ();
182     return (-1);
183   }
184 
185   std::string line;
186   std::vector<std::string> st;
187   boost::filesystem::path parent_path = mtl_file_path.c_str ();
188   parent_path = parent_path.parent_path ();
189 
190   try
191   {
192     while (!mtl_file.eof ())
193     {
194       getline (mtl_file, line);
195       // Ignore empty lines
196       if (line.empty())
197         continue;
198 
199       // Tokenize the line
200       std::stringstream sstream (line);
201       sstream.imbue (std::locale::classic ());
202       line = sstream.str ();
203       boost::trim (line);
204       boost::split (st, line, boost::is_any_of ("\t\r "), boost::token_compress_on);
205       // Ignore comments
206       if (st[0] == "#")
207         continue;
208 
209       if (st[0] == "newmtl")
210       {
211         materials_.emplace_back();
212         materials_.back ().tex_name = st[1];
213         continue;
214       }
215 
216       if (st[0] == "Ka" || st[0] == "Kd" || st[0] == "Ks")
217       {
218         if (st[1] == "spectral")
219         {
220           PCL_ERROR ("[pcl::MTLReader::read] Can't handle spectral files!\n");
221           mtl_file.close ();
222           materials_.clear ();
223           return (-1);
224         }
225         pcl::TexMaterial::RGB *rgb = &materials_.back ().tex_Ka;
226         if (st[0] == "Kd")
227           rgb = &materials_.back ().tex_Kd;
228         else if (st[0] == "Ks")
229           rgb = &materials_.back ().tex_Ks;
230 
231         if (st[1] == "xyz")
232         {
233           if (fillRGBfromXYZ (st, *rgb))
234           {
235             PCL_ERROR ("[pcl::MTLReader::read] Could not convert %s to RGB values\n",
236                        line.c_str ());
237             mtl_file.close ();
238             materials_.clear ();
239             return (-1);
240           }
241         }
242         else
243         {
244           if (fillRGBfromRGB (st, *rgb))
245           {
246             PCL_ERROR ("[pcl::MTLReader::read] Could not convert %s to RGB values\n",
247                        line.c_str ());
248             mtl_file.close ();
249             materials_.clear ();
250             return (-1);
251           }
252         }
253         continue;
254       }
255 
256       if (st[0] == "illum")
257       {
258         try
259         {
260           materials_.back ().tex_illum = boost::lexical_cast<int> (st[1]);
261         }
262         catch (boost::bad_lexical_cast &)
263         {
264           PCL_ERROR ("[pcl::MTLReader::read] Could not convert %s to illumination model\n",
265                      line.c_str ());
266           mtl_file.close ();
267           materials_.clear ();
268           return (-1);
269         }
270         continue;
271       }
272 
273       if (st[0] == "d" || st[0] == "Tr")
274       {
275         bool reverse = (st[0] == "Tr");
276         try
277         {
278           materials_.back ().tex_d = boost::lexical_cast<float> (st[st.size () > 2 ? 2:1]);
279           if (reverse)
280             materials_.back ().tex_d = 1.f - materials_.back ().tex_d;
281         }
282         catch (boost::bad_lexical_cast &)
283         {
284           PCL_ERROR ("[pcl::MTLReader::read] Could not convert %s to transparency value\n",
285                      line.c_str ());
286           mtl_file.close ();
287           materials_.clear ();
288           return (-1);
289         }
290         continue;
291       }
292 
293       if (st[0] == "Ns")
294       {
295         try
296         {
297           materials_.back ().tex_Ns = boost::lexical_cast<float> (st[1]);
298         }
299         catch (boost::bad_lexical_cast &)
300         {
301           PCL_ERROR ("[pcl::MTLReader::read] Could not convert %s to shininess value\n",
302                      line.c_str ());
303           mtl_file.close ();
304           materials_.clear ();
305           return (-1);
306         }
307         continue;
308       }
309 
310       if (st[0] == "map_Kd")
311       {
312         boost::filesystem::path full_path = parent_path;
313         full_path/= st.back ().c_str ();
314         materials_.back ().tex_file = full_path.string ();
315         continue;
316       }
317       // other elements? we don't care for now
318     }
319   }
320   catch (const char *exception)
321   {
322     PCL_ERROR ("[pcl::MTLReader::read] %s\n", exception);
323     mtl_file.close ();
324     materials_.clear ();
325     return (-1);
326   }
327 
328   return (0);
329 }
330 
331 int
readHeader(const std::string & file_name,pcl::PCLPointCloud2 & cloud,Eigen::Vector4f & origin,Eigen::Quaternionf & orientation,int & file_version,int & data_type,unsigned int & data_idx,const int offset)332 pcl::OBJReader::readHeader (const std::string &file_name, pcl::PCLPointCloud2 &cloud,
333                             Eigen::Vector4f &origin, Eigen::Quaternionf &orientation,
334                             int &file_version, int &data_type, unsigned int &data_idx,
335                             const int offset)
336 {
337   origin       = Eigen::Vector4f::Zero ();
338   orientation  = Eigen::Quaternionf::Identity ();
339   file_version = 0;
340   cloud.width  = cloud.height = cloud.point_step = cloud.row_step = 0;
341   cloud.data.clear ();
342   data_type = 0;
343   data_idx = offset;
344 
345   std::ifstream fs;
346   std::string line;
347 
348   if (file_name.empty() || !boost::filesystem::exists (file_name))
349   {
350     PCL_ERROR ("[pcl::OBJReader::readHeader] Could not find file '%s'.\n", file_name.c_str ());
351     return (-1);
352   }
353 
354   // Open file in binary mode to avoid problem of
355   // std::getline() corrupting the result of ifstream::tellg()
356   fs.open (file_name.c_str (), std::ios::binary);
357   if (!fs.is_open () || fs.fail ())
358   {
359     PCL_ERROR ("[pcl::OBJReader::readHeader] Could not open file '%s'! Error : %s\n", file_name.c_str (), strerror(errno));
360     fs.close ();
361     return (-1);
362   }
363 
364   // Seek at the given offset
365   fs.seekg (offset, std::ios::beg);
366 
367   // Read the header and fill it in with wonderful values
368   bool vertex_normal_found = false;
369   bool vertex_texture_found = false;
370   // Material library, skip for now!
371   // bool material_found = false;
372   std::vector<std::string> material_files;
373   std::size_t nr_point = 0;
374 
375   try
376   {
377     while (!fs.eof ())
378     {
379       getline (fs, line);
380       // Ignore empty lines
381       if (line.empty())
382         continue;
383 
384       // Trim the line
385       boost::trim (line);
386 
387       // Ignore comments
388       if (line[0] == '#')
389         continue;
390 
391       // Vertex, vertex texture or vertex normal
392       if (line[0] == 'v')
393       {
394         // Vertex (v)
395         if (line[1] == ' ') {
396           ++nr_point;
397           continue;
398         }
399 
400         // Vertex texture (vt)
401         if ((line[1] == 't') && !vertex_texture_found)
402         {
403           vertex_texture_found = true;
404           continue;
405         }
406 
407         // Vertex normal (vn)
408         if ((line[1] == 'n') && !vertex_normal_found)
409         {
410           vertex_normal_found = true;
411           continue;
412         }
413       }
414 
415       // Material library, skip for now!
416       if (line.substr (0, 6) == "mtllib")
417       {
418         std::vector<std::string> st;
419         boost::split(st, line, boost::is_any_of("\t\r "), boost::token_compress_on);
420         material_files.push_back (st.at (1));
421         continue;
422       }
423     }
424   }
425   catch (const char *exception)
426   {
427     PCL_ERROR ("[pcl::OBJReader::readHeader] %s\n", exception);
428     fs.close ();
429     return (-1);
430   }
431 
432   if (!nr_point)
433   {
434     PCL_ERROR ("[pcl::OBJReader::readHeader] No vertices found!\n");
435     fs.close ();
436     return (-1);
437   }
438 
439   int field_offset = 0;
440   for (int i = 0; i < 3; ++i, field_offset += 4)
441   {
442     cloud.fields.emplace_back();
443     cloud.fields[i].offset   = field_offset;
444     cloud.fields[i].datatype = pcl::PCLPointField::FLOAT32;
445     cloud.fields[i].count    = 1;
446   }
447 
448   cloud.fields[0].name = "x";
449   cloud.fields[1].name = "y";
450   cloud.fields[2].name = "z";
451 
452   if (vertex_normal_found)
453   {
454     std::string normals_names[3] = { "normal_x", "normal_y", "normal_z" };
455     for (int i = 0; i < 3; ++i, field_offset += 4)
456     {
457       cloud.fields.emplace_back();
458       pcl::PCLPointField& last = cloud.fields.back ();
459       last.name     = normals_names[i];
460       last.offset   = field_offset;
461       last.datatype = pcl::PCLPointField::FLOAT32;
462       last.count    = 1;
463     }
464   }
465 
466   if (!material_files.empty ())
467   {
468     for (const auto &material_file : material_files)
469     {
470       MTLReader companion;
471       if (companion.read (file_name, material_file))
472         PCL_WARN ("[pcl::OBJReader::readHeader] Problem reading material file %s\n",
473                   material_file.c_str ());
474       companions_.push_back (companion);
475     }
476   }
477 
478   cloud.point_step = field_offset;
479   cloud.width      = nr_point;
480   cloud.height     = 1;
481   cloud.row_step   = cloud.point_step * cloud.width;
482   cloud.is_dense   = true;
483   cloud.data.resize (cloud.point_step * nr_point);
484   fs.close ();
485   return (0);
486 }
487 
488 int
read(const std::string & file_name,pcl::PCLPointCloud2 & cloud,const int offset)489 pcl::OBJReader::read (const std::string &file_name, pcl::PCLPointCloud2 &cloud, const int offset)
490 {
491   int file_version;
492   Eigen::Vector4f origin;
493   Eigen::Quaternionf orientation;
494   return (read (file_name, cloud, origin, orientation, file_version, offset));
495 }
496 
497 int
read(const std::string & file_name,pcl::PCLPointCloud2 & cloud,Eigen::Vector4f & origin,Eigen::Quaternionf & orientation,int & file_version,const int offset)498 pcl::OBJReader::read (const std::string &file_name, pcl::PCLPointCloud2 &cloud,
499                       Eigen::Vector4f &origin, Eigen::Quaternionf &orientation,
500                       int &file_version, const int offset)
501 {
502   pcl::console::TicToc tt;
503   tt.tic ();
504 
505   int data_type;
506   unsigned int data_idx;
507   if (readHeader (file_name, cloud, origin, orientation, file_version, data_type, data_idx, offset))
508   {
509     PCL_ERROR ("[pcl::OBJReader::read] Problem reading header!\n");
510     return (-1);
511   }
512 
513   std::ifstream fs;
514   fs.open (file_name.c_str (), std::ios::binary);
515   if (!fs.is_open () || fs.fail ())
516   {
517     PCL_ERROR ("[pcl::OBJReader::readHeader] Could not open file '%s'! Error : %s\n",
518                file_name.c_str (), strerror(errno));
519     fs.close ();
520     return (-1);
521   }
522 
523   // Seek at the given offset
524   fs.seekg (data_idx, std::ios::beg);
525 
526   // Get normal_x and rgba fields indices
527   int normal_x_field = -1;
528   // std::size_t rgba_field = 0;
529   for (std::size_t i = 0; i < cloud.fields.size (); ++i)
530     if (cloud.fields[i].name == "normal_x")
531     {
532       normal_x_field = i;
533       break;
534     }
535   // else if (cloud.fields[i].name == "rgba")
536   //   rgba_field = i;
537 
538   std::vector<std::string> st;
539   try
540   {
541     uindex_t point_idx = 0;
542     uindex_t normal_idx = 0;
543 
544     while (!fs.eof ())
545     {
546       std::string line;
547       getline (fs, line);
548       // Ignore empty lines
549       if (line.empty())
550         continue;
551 
552       // Tokenize the line
553       std::stringstream sstream (line);
554       sstream.imbue (std::locale::classic ());
555       line = sstream.str ();
556       boost::trim (line);
557       boost::split (st, line, boost::is_any_of ("\t\r "), boost::token_compress_on);
558 
559       // Ignore comments
560       if (st[0] == "#")
561         continue;
562 
563       // Vertex
564       if (st[0] == "v")
565       {
566         try
567         {
568           for (int i = 1, f = 0; i < 4; ++i, ++f)
569           {
570             float value = boost::lexical_cast<float> (st[i]);
571             memcpy (&cloud.data[point_idx * cloud.point_step + cloud.fields[f].offset],
572                 &value,
573                 sizeof (float));
574           }
575           ++point_idx;
576         }
577         catch (const boost::bad_lexical_cast&)
578         {
579           PCL_ERROR ("Unable to convert %s to vertex coordinates!\n", line.c_str ());
580           return (-1);
581         }
582         continue;
583       }
584 
585       // Vertex normal
586       if (st[0] == "vn")
587       {
588         if (normal_idx >= cloud.width)
589         {
590           if (normal_idx == cloud.width)
591             PCL_WARN ("[pcl:OBJReader] Too many vertex normals (expected %d), skipping remaining normals.\n", cloud.width, normal_idx + 1);
592           ++normal_idx;
593           continue;
594         }
595         try
596         {
597           for (int i = 1, f = normal_x_field; i < 4; ++i, ++f)
598           {
599             float value = boost::lexical_cast<float> (st[i]);
600             memcpy (&cloud.data[normal_idx * cloud.point_step + cloud.fields[f].offset],
601                 &value,
602                 sizeof (float));
603           }
604           ++normal_idx;
605         }
606         catch (const boost::bad_lexical_cast&)
607         {
608           PCL_ERROR ("Unable to convert line %s to vertex normal!\n", line.c_str ());
609           return (-1);
610         }
611         continue;
612       }
613     }
614   }
615   catch (const char *exception)
616   {
617     PCL_ERROR ("[pcl::OBJReader::read] %s\n", exception);
618     fs.close ();
619     return (-1);
620   }
621 
622   double total_time = tt.toc ();
623   PCL_DEBUG ("[pcl::OBJReader::read] Loaded %s as a dense cloud in %g ms with %d points. Available dimensions: %s.\n",
624              file_name.c_str (), total_time,
625              cloud.width * cloud.height, pcl::getFieldsList (cloud).c_str ());
626   fs.close ();
627   return (0);
628 }
629 
630 int
read(const std::string & file_name,pcl::TextureMesh & mesh,const int offset)631 pcl::OBJReader::read (const std::string &file_name, pcl::TextureMesh &mesh, const int offset)
632 {
633   int file_version;
634   Eigen::Vector4f origin;
635   Eigen::Quaternionf orientation;
636   return (read (file_name, mesh, origin, orientation, file_version, offset));
637 }
638 
639 int
read(const std::string & file_name,pcl::TextureMesh & mesh,Eigen::Vector4f & origin,Eigen::Quaternionf & orientation,int & file_version,const int offset)640 pcl::OBJReader::read (const std::string &file_name, pcl::TextureMesh &mesh,
641                       Eigen::Vector4f &origin, Eigen::Quaternionf &orientation,
642                       int &file_version, const int offset)
643 {
644   pcl::console::TicToc tt;
645   tt.tic ();
646 
647   int data_type;
648   unsigned int data_idx;
649   if (readHeader (file_name, mesh.cloud, origin, orientation, file_version, data_type, data_idx, offset))
650   {
651     PCL_ERROR ("[pcl::OBJReader::read] Problem reading header!\n");
652     return (-1);
653   }
654 
655   std::ifstream fs;
656   fs.open (file_name.c_str (), std::ios::binary);
657   if (!fs.is_open () || fs.fail ())
658   {
659     PCL_ERROR ("[pcl::OBJReader::readHeader] Could not open file '%s'! Error : %s\n",
660                file_name.c_str (), strerror(errno));
661     fs.close ();
662     return (-1);
663   }
664 
665   // Seek at the given offset
666   fs.seekg (data_idx, std::ios::beg);
667 
668   // Get normal_x and rgba fields indices
669   int normal_x_field = -1;
670   // std::size_t rgba_field = 0;
671   for (std::size_t i = 0; i < mesh.cloud.fields.size (); ++i)
672     if (mesh.cloud.fields[i].name == "normal_x")
673     {
674       normal_x_field = i;
675       break;
676     }
677 
678   std::size_t v_idx = 0;
679   std::size_t f_idx = 0;
680   std::string line;
681   std::vector<std::string> st;
682   std::vector<Eigen::Vector2f, Eigen::aligned_allocator<Eigen::Vector2f> > coordinates;
683   try
684   {
685     std::size_t vn_idx = 0;
686     std::size_t vt_idx = 0;
687 
688     while (!fs.eof ())
689     {
690       getline (fs, line);
691       // Ignore empty lines
692       if (line.empty())
693         continue;
694 
695       // Tokenize the line
696       std::stringstream sstream (line);
697       sstream.imbue (std::locale::classic ());
698       line = sstream.str ();
699       boost::trim (line);
700       boost::split (st, line, boost::is_any_of ("\t\r "), boost::token_compress_on);
701 
702       // Ignore comments
703       if (st[0] == "#")
704         continue;
705       // Vertex
706       if (st[0] == "v")
707       {
708         try
709         {
710           for (int i = 1, f = 0; i < 4; ++i, ++f)
711           {
712             float value = boost::lexical_cast<float> (st[i]);
713             memcpy (&mesh.cloud.data[v_idx * mesh.cloud.point_step + mesh.cloud.fields[f].offset],
714                 &value,
715                 sizeof (float));
716           }
717           ++v_idx;
718         }
719         catch (const boost::bad_lexical_cast&)
720         {
721           PCL_ERROR ("Unable to convert %s to vertex coordinates!\n", line.c_str ());
722           return (-1);
723         }
724         continue;
725       }
726       // Vertex normal
727       if (st[0] == "vn")
728       {
729         try
730         {
731           for (int i = 1, f = normal_x_field; i < 4; ++i, ++f)
732           {
733             float value = boost::lexical_cast<float> (st[i]);
734             memcpy (&mesh.cloud.data[vn_idx * mesh.cloud.point_step + mesh.cloud.fields[f].offset],
735                 &value,
736                 sizeof (float));
737           }
738           ++vn_idx;
739         }
740         catch (const boost::bad_lexical_cast&)
741         {
742           PCL_ERROR ("Unable to convert line %s to vertex normal!\n", line.c_str ());
743           return (-1);
744         }
745         continue;
746       }
747       // Texture coordinates
748       if (st[0] == "vt")
749       {
750         try
751         {
752           Eigen::Vector3f c (0, 0, 0);
753           for (std::size_t i = 1; i < st.size (); ++i)
754             c[i-1] = boost::lexical_cast<float> (st[i]);
755           if (c[2] == 0)
756             coordinates.emplace_back(c[0], c[1]);
757           else
758             coordinates.emplace_back(c[0]/c[2], c[1]/c[2]);
759           ++vt_idx;
760         }
761         catch (const boost::bad_lexical_cast&)
762         {
763           PCL_ERROR ("Unable to convert line %s to texture coordinates!\n", line.c_str ());
764           return (-1);
765         }
766         continue;
767       }
768       // Material
769       if (st[0] == "usemtl")
770       {
771         mesh.tex_polygons.emplace_back();
772         mesh.tex_materials.emplace_back();
773         for (const auto &companion : companions_)
774         {
775           auto mat_it = companion.getMaterial (st[1]);
776           if (mat_it != companion.materials_.end ())
777           {
778             mesh.tex_materials.back () = *mat_it;
779             break;
780           }
781         }
782         // We didn't find the appropriate material so we create it here with name only.
783         if (mesh.tex_materials.back ().tex_name.empty())
784           mesh.tex_materials.back ().tex_name = st[1];
785         mesh.tex_coordinates.push_back (coordinates);
786         coordinates.clear ();
787         continue;
788       }
789       // Face
790       if (st[0] == "f")
791       {
792         //We only care for vertices indices
793         pcl::Vertices face_v; face_v.vertices.resize (st.size () - 1);
794         for (std::size_t i = 1; i < st.size (); ++i)
795         {
796           int v;
797           sscanf (st[i].c_str (), "%d", &v);
798           v = (v < 0) ? v_idx + v : v - 1;
799           face_v.vertices[i-1] = v;
800         }
801         mesh.tex_polygons.back ().push_back (face_v);
802         ++f_idx;
803         continue;
804       }
805     }
806   }
807   catch (const char *exception)
808   {
809     PCL_ERROR ("[pcl::OBJReader::read] %s\n", exception);
810     fs.close ();
811     return (-1);
812   }
813 
814   double total_time = tt.toc ();
815   PCL_DEBUG ("[pcl::OBJReader::read] Loaded %s as a TextureMesh in %g ms with %g points, %g texture materials, %g polygons.\n",
816              file_name.c_str (), total_time,
817              v_idx -1, mesh.tex_materials.size (), f_idx -1);
818   fs.close ();
819   return (0);
820 }
821 
822 int
read(const std::string & file_name,pcl::PolygonMesh & mesh,const int offset)823 pcl::OBJReader::read (const std::string &file_name, pcl::PolygonMesh &mesh, const int offset)
824 {
825   int file_version;
826   Eigen::Vector4f origin;
827   Eigen::Quaternionf orientation;
828   return (read (file_name, mesh, origin, orientation, file_version, offset));
829 }
830 
831 int
read(const std::string & file_name,pcl::PolygonMesh & mesh,Eigen::Vector4f & origin,Eigen::Quaternionf & orientation,int & file_version,const int offset)832 pcl::OBJReader::read (const std::string &file_name, pcl::PolygonMesh &mesh,
833                       Eigen::Vector4f &origin, Eigen::Quaternionf &orientation,
834                       int &file_version, const int offset)
835 {
836   pcl::console::TicToc tt;
837   tt.tic ();
838 
839   int data_type;
840   unsigned int data_idx;
841   if (readHeader (file_name, mesh.cloud, origin, orientation, file_version, data_type, data_idx, offset))
842   {
843     PCL_ERROR ("[pcl::OBJReader::read] Problem reading header!\n");
844     return (-1);
845   }
846 
847   std::ifstream fs;
848   fs.open (file_name.c_str (), std::ios::binary);
849   if (!fs.is_open () || fs.fail ())
850   {
851     PCL_ERROR ("[pcl::OBJReader::readHeader] Could not open file '%s'! Error : %s\n",
852                file_name.c_str (), strerror(errno));
853     fs.close ();
854     return (-1);
855   }
856 
857   // Seek at the given offset
858   fs.seekg (data_idx, std::ios::beg);
859 
860   // Get normal_x and rgba fields indices
861   int normal_x_field = -1;
862   // std::size_t rgba_field = 0;
863   for (std::size_t i = 0; i < mesh.cloud.fields.size (); ++i)
864     if (mesh.cloud.fields[i].name == "normal_x")
865     {
866       normal_x_field = i;
867       break;
868     }
869 
870   std::vector<std::string> st;
871   try
872   {
873     std::size_t v_idx = 0;
874     std::size_t vn_idx = 0;
875 
876     while (!fs.eof ())
877     {
878       std::string line;
879       getline (fs, line);
880       // Ignore empty lines
881       if (line.empty())
882         continue;
883 
884       // Tokenize the line
885       std::stringstream sstream (line);
886       sstream.imbue (std::locale::classic ());
887       line = sstream.str ();
888       boost::trim (line);
889       boost::split (st, line, boost::is_any_of ("\t\r "), boost::token_compress_on);
890 
891       // Ignore comments
892       if (st[0] == "#")
893         continue;
894 
895       // Vertex
896       if (st[0] == "v")
897       {
898         try
899         {
900           for (int i = 1, f = 0; i < 4; ++i, ++f)
901           {
902             float value = boost::lexical_cast<float> (st[i]);
903             memcpy (&mesh.cloud.data[v_idx * mesh.cloud.point_step + mesh.cloud.fields[f].offset],
904                 &value,
905                 sizeof (float));
906           }
907           ++v_idx;
908         }
909         catch (const boost::bad_lexical_cast&)
910         {
911           PCL_ERROR ("Unable to convert %s to vertex coordinates!\n", line.c_str ());
912           return (-1);
913         }
914         continue;
915       }
916 
917       // Vertex normal
918       if (st[0] == "vn")
919       {
920         try
921         {
922           for (int i = 1, f = normal_x_field; i < 4; ++i, ++f)
923           {
924             float value = boost::lexical_cast<float> (st[i]);
925             memcpy (&mesh.cloud.data[vn_idx * mesh.cloud.point_step + mesh.cloud.fields[f].offset],
926                 &value,
927                 sizeof (float));
928           }
929           ++vn_idx;
930         }
931         catch (const boost::bad_lexical_cast&)
932         {
933           PCL_ERROR ("Unable to convert line %s to vertex normal!\n", line.c_str ());
934           return (-1);
935         }
936         continue;
937       }
938 
939       // Face
940       if (st[0] == "f")
941       {
942         pcl::Vertices face_vertices; face_vertices.vertices.resize (st.size () - 1);
943         for (std::size_t i = 1; i < st.size (); ++i)
944         {
945           int v;
946           sscanf (st[i].c_str (), "%d", &v);
947           v = (v < 0) ? v_idx + v : v - 1;
948           face_vertices.vertices[i - 1] = v;
949         }
950         mesh.polygons.push_back (face_vertices);
951         continue;
952       }
953     }
954   }
955   catch (const char *exception)
956   {
957     PCL_ERROR ("[pcl::OBJReader::read] %s\n", exception);
958     fs.close ();
959     return (-1);
960   }
961 
962   double total_time = tt.toc ();
963   PCL_DEBUG ("[pcl::OBJReader::read] Loaded %s as a PolygonMesh in %g ms with %g points and %g polygons.\n",
964              file_name.c_str (), total_time,
965              mesh.cloud.width * mesh.cloud.height, mesh.polygons.size ());
966   fs.close ();
967   return (0);
968 }
969 
970 int
saveOBJFile(const std::string & file_name,const pcl::TextureMesh & tex_mesh,unsigned precision)971 pcl::io::saveOBJFile (const std::string &file_name,
972                       const pcl::TextureMesh &tex_mesh, unsigned precision)
973 {
974   if (tex_mesh.cloud.data.empty ())
975   {
976     PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no data!\n");
977     return (-1);
978   }
979   // Open file
980   std::ofstream fs;
981   fs.precision (precision);
982   fs.open (file_name.c_str ());
983 
984   // Define material file
985   std::string mtl_file_name = file_name.substr (0, file_name.find_last_of ('.')) + ".mtl";
986   // Strip path for "mtllib" command
987   std::string mtl_file_name_nopath = mtl_file_name;
988   mtl_file_name_nopath.erase (0, mtl_file_name.find_last_of ('/') + 1);
989 
990   /* Write 3D information */
991   // number of points
992   unsigned nr_points  = tex_mesh.cloud.width * tex_mesh.cloud.height;
993   unsigned point_size = static_cast<unsigned> (tex_mesh.cloud.data.size () / nr_points);
994 
995   // mesh size
996   unsigned nr_meshes = static_cast<unsigned> (tex_mesh.tex_polygons.size ());
997   // number of faces for header
998   unsigned nr_faces = 0;
999   for (unsigned m = 0; m < nr_meshes; ++m)
1000     nr_faces += static_cast<unsigned> (tex_mesh.tex_polygons[m].size ());
1001 
1002   // Write the header information
1003   fs << "####" << '\n';
1004   fs << "# OBJ dataFile simple version. File name: " << file_name << '\n';
1005   fs << "# Vertices: " << nr_points << '\n';
1006   fs << "# Faces: " <<nr_faces << '\n';
1007   fs << "# Material information:" << '\n';
1008   fs << "mtllib " << mtl_file_name_nopath << '\n';
1009   fs << "####" << '\n';
1010 
1011   // Write vertex coordinates
1012   fs << "# Vertices" << '\n';
1013   for (unsigned i = 0; i < nr_points; ++i)
1014   {
1015     int xyz = 0;
1016     // "v" just be written one
1017     bool v_written = false;
1018     for (std::size_t d = 0; d < tex_mesh.cloud.fields.size (); ++d)
1019     {
1020       // adding vertex
1021       if ((tex_mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) && (
1022           tex_mesh.cloud.fields[d].name == "x" ||
1023           tex_mesh.cloud.fields[d].name == "y" ||
1024           tex_mesh.cloud.fields[d].name == "z"))
1025       {
1026         if (!v_written)
1027         {
1028            // write vertices beginning with v
1029           fs << "v ";
1030           v_written = true;
1031         }
1032         float value;
1033         memcpy (&value, &tex_mesh.cloud.data[i * point_size + tex_mesh.cloud.fields[d].offset], sizeof (float));
1034         fs << value;
1035         if (++xyz == 3)
1036           break;
1037         fs << " ";
1038       }
1039     }
1040     if (xyz != 3)
1041     {
1042       PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no XYZ data!\n");
1043       return (-2);
1044     }
1045     fs << '\n';
1046   }
1047   fs << "# "<< nr_points <<" vertices" << '\n';
1048 
1049   // Write vertex normals
1050   for (unsigned i = 0; i < nr_points; ++i)
1051   {
1052     int xyz = 0;
1053     // "vn" just be written one
1054     bool v_written = false;
1055     for (std::size_t d = 0; d < tex_mesh.cloud.fields.size (); ++d)
1056     {
1057       // adding vertex
1058       if ((tex_mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) && (
1059           tex_mesh.cloud.fields[d].name == "normal_x" ||
1060           tex_mesh.cloud.fields[d].name == "normal_y" ||
1061           tex_mesh.cloud.fields[d].name == "normal_z"))
1062       {
1063     	  if (!v_written)
1064     	  {
1065           // write vertices beginning with vn
1066           fs << "vn ";
1067           v_written = true;
1068     	  }
1069         float value;
1070         memcpy (&value, &tex_mesh.cloud.data[i * point_size + tex_mesh.cloud.fields[d].offset], sizeof (float));
1071         fs << value;
1072         if (++xyz == 3)
1073           break;
1074         fs << " ";
1075       }
1076     }
1077     if (xyz != 3)
1078     {
1079       PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no normals!\n");
1080       return (-2);
1081     }
1082     fs << '\n';
1083   }
1084   // Write vertex texture with "vt" (adding latter)
1085 
1086   for (unsigned m = 0; m < nr_meshes; ++m)
1087   {
1088     fs << "# " << tex_mesh.tex_coordinates[m].size() << " vertex textures in submesh " << m <<  '\n';
1089     for (const auto &coordinate : tex_mesh.tex_coordinates[m])
1090     {
1091       fs << "vt ";
1092       fs <<  coordinate[0] << " " << coordinate[1] << '\n';
1093     }
1094   }
1095 
1096   unsigned f_idx = 0;
1097 
1098   // int idx_vt =0;
1099   for (unsigned m = 0; m < nr_meshes; ++m)
1100   {
1101     if (m > 0) f_idx += static_cast<unsigned> (tex_mesh.tex_polygons[m-1].size ());
1102 
1103     fs << "# The material will be used for mesh " << m << '\n';
1104     fs << "usemtl " <<  tex_mesh.tex_materials[m].tex_name << '\n';
1105     fs << "# Faces" << '\n';
1106 
1107     for (std::size_t i = 0; i < tex_mesh.tex_polygons[m].size(); ++i)
1108     {
1109       // Write faces with "f"
1110       fs << "f";
1111       // There's one UV per vertex per face, i.e., the same vertex can have
1112       // different UV depending on the face.
1113       for (std::size_t j = 0; j < tex_mesh.tex_polygons[m][i].vertices.size (); ++j)
1114       {
1115         std::uint32_t idx = tex_mesh.tex_polygons[m][i].vertices[j] + 1;
1116         fs << " " << idx
1117            << "/" << tex_mesh.tex_polygons[m][i].vertices.size () * (i+f_idx) +j+1
1118            << "/" << idx; // vertex index in obj file format starting with 1
1119       }
1120       fs << '\n';
1121     }
1122     fs << "# "<< tex_mesh.tex_polygons[m].size() << " faces in mesh " << m << '\n';
1123   }
1124   fs << "# End of File" << std::flush;
1125 
1126   // Close obj file
1127   fs.close ();
1128 
1129   /* Write material definition for OBJ file*/
1130   // Open file
1131 
1132   std::ofstream m_fs;
1133   m_fs.precision (precision);
1134   m_fs.open (mtl_file_name.c_str ());
1135 
1136   // default
1137   m_fs << "#" << '\n';
1138   m_fs << "# Wavefront material file" << '\n';
1139   m_fs << "#" << '\n';
1140   for(unsigned m = 0; m < nr_meshes; ++m)
1141   {
1142     m_fs << "newmtl " << tex_mesh.tex_materials[m].tex_name << '\n';
1143     m_fs << "Ka "<< tex_mesh.tex_materials[m].tex_Ka.r << " " << tex_mesh.tex_materials[m].tex_Ka.g << " " << tex_mesh.tex_materials[m].tex_Ka.b << '\n'; // defines the ambient color of the material to be (r,g,b).
1144     m_fs << "Kd "<< tex_mesh.tex_materials[m].tex_Kd.r << " " << tex_mesh.tex_materials[m].tex_Kd.g << " " << tex_mesh.tex_materials[m].tex_Kd.b << '\n'; // defines the diffuse color of the material to be (r,g,b).
1145     m_fs << "Ks "<< tex_mesh.tex_materials[m].tex_Ks.r << " " << tex_mesh.tex_materials[m].tex_Ks.g << " " << tex_mesh.tex_materials[m].tex_Ks.b << '\n'; // defines the specular color of the material to be (r,g,b). This color shows up in highlights.
1146     m_fs << "d " << tex_mesh.tex_materials[m].tex_d << '\n'; // defines the transparency of the material to be alpha.
1147     m_fs << "Ns "<< tex_mesh.tex_materials[m].tex_Ns  << '\n'; // defines the shininess of the material to be s.
1148     m_fs << "illum "<< tex_mesh.tex_materials[m].tex_illum << '\n'; // denotes the illumination model used by the material.
1149                                             // illum = 1 indicates a flat material with no specular highlights, so the value of Ks is not used.
1150                                             // illum = 2 denotes the presence of specular highlights, and so a specification for Ks is required.
1151     m_fs << "map_Kd " << tex_mesh.tex_materials[m].tex_file << '\n';
1152     m_fs << "###" << '\n';
1153   }
1154   m_fs.close ();
1155   return (0);
1156 }
1157 
1158 int
saveOBJFile(const std::string & file_name,const pcl::PolygonMesh & mesh,unsigned precision)1159 pcl::io::saveOBJFile (const std::string &file_name,
1160                       const pcl::PolygonMesh &mesh, unsigned precision)
1161 {
1162   if (mesh.cloud.data.empty ())
1163   {
1164     PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no data!\n");
1165     return (-1);
1166   }
1167   // Open file
1168   std::ofstream fs;
1169   fs.precision (precision);
1170   fs.open (file_name.c_str ());
1171 
1172   /* Write 3D information */
1173   // number of points
1174   int nr_points  = mesh.cloud.width * mesh.cloud.height;
1175   // point size
1176   unsigned point_size = static_cast<unsigned> (mesh.cloud.data.size () / nr_points);
1177   // number of faces for header
1178   unsigned nr_faces = static_cast<unsigned> (mesh.polygons.size ());
1179   // Do we have vertices normals?
1180   int normal_index = getFieldIndex (mesh.cloud, "normal_x");
1181 
1182   // Write the header information
1183   fs << "####" << '\n';
1184   fs << "# OBJ dataFile simple version. File name: " << file_name << '\n';
1185   fs << "# Vertices: " << nr_points << '\n';
1186   if (normal_index != -1)
1187     fs << "# Vertices normals : " << nr_points << '\n';
1188   fs << "# Faces: " <<nr_faces << '\n';
1189   fs << "####" << '\n';
1190 
1191   // Write vertex coordinates
1192   fs << "# List of Vertices, with (x,y,z) coordinates, w is optional." << '\n';
1193   for (int i = 0; i < nr_points; ++i)
1194   {
1195     int xyz = 0;
1196     for (std::size_t d = 0; d < mesh.cloud.fields.size (); ++d)
1197     {
1198       // adding vertex
1199       if ((mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) && (
1200           mesh.cloud.fields[d].name == "x" ||
1201           mesh.cloud.fields[d].name == "y" ||
1202           mesh.cloud.fields[d].name == "z"))
1203       {
1204         if (mesh.cloud.fields[d].name == "x")
1205            // write vertices beginning with v
1206           fs << "v ";
1207 
1208         float value;
1209         memcpy (&value, &mesh.cloud.data[i * point_size + mesh.cloud.fields[d].offset], sizeof (float));
1210         fs << value;
1211         if (++xyz == 3)
1212           break;
1213         fs << " ";
1214       }
1215     }
1216     if (xyz != 3)
1217     {
1218       PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no XYZ data!\n");
1219       return (-2);
1220     }
1221     fs << '\n';
1222   }
1223 
1224   fs << "# "<< nr_points <<" vertices" << '\n';
1225 
1226   if(normal_index != -1)
1227   {
1228     fs << "# Normals in (x,y,z) form; normals might not be unit." <<  '\n';
1229     // Write vertex normals
1230     for (int i = 0; i < nr_points; ++i)
1231     {
1232       int nxyz = 0;
1233       for (std::size_t d = 0; d < mesh.cloud.fields.size (); ++d)
1234       {
1235         // adding vertex
1236         if ((mesh.cloud.fields[d].datatype == pcl::PCLPointField::FLOAT32) && (
1237               mesh.cloud.fields[d].name == "normal_x" ||
1238               mesh.cloud.fields[d].name == "normal_y" ||
1239               mesh.cloud.fields[d].name == "normal_z"))
1240         {
1241           if (mesh.cloud.fields[d].name == "normal_x")
1242             // write vertices beginning with vn
1243             fs << "vn ";
1244 
1245           float value;
1246           memcpy (&value, &mesh.cloud.data[i * point_size + mesh.cloud.fields[d].offset], sizeof (float));
1247           fs << value;
1248           if (++nxyz == 3)
1249             break;
1250           fs << " ";
1251         }
1252       }
1253       if (nxyz != 3)
1254       {
1255         PCL_ERROR ("[pcl::io::saveOBJFile] Input point cloud has no normals!\n");
1256         return (-2);
1257       }
1258       fs << '\n';
1259     }
1260 
1261     fs << "# "<< nr_points <<" vertices normals" << '\n';
1262   }
1263 
1264   fs << "# Face Definitions" << '\n';
1265   // Write down faces
1266   if(normal_index == -1)
1267   {
1268     for(unsigned i = 0; i < nr_faces; i++)
1269     {
1270       fs << "f ";
1271       for (std::size_t j = 0; j < mesh.polygons[i].vertices.size () - 1; ++j)
1272         fs << mesh.polygons[i].vertices[j] + 1 << " ";
1273       fs << mesh.polygons[i].vertices.back() + 1 << '\n';
1274     }
1275   }
1276   else
1277   {
1278     for(unsigned i = 0; i < nr_faces; i++)
1279     {
1280       fs << "f ";
1281       for (std::size_t j = 0; j < mesh.polygons[i].vertices.size () - 1; ++j)
1282         fs << mesh.polygons[i].vertices[j] + 1 << "//" << mesh.polygons[i].vertices[j] + 1 << " ";
1283       fs << mesh.polygons[i].vertices.back() + 1 << "//" << mesh.polygons[i].vertices.back() + 1 << '\n';
1284     }
1285   }
1286   fs << "# End of File" << std::endl;
1287 
1288   // Close obj file
1289   fs.close ();
1290   return 0;
1291 }
1292