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