1 // Copyright (c) 2018-2020  GeometryFactory Sarl (France).
2 // All rights reserved.
3 //
4 // Licensees holding a valid commercial license may use this file in
5 // accordance with the commercial license agreement provided with the software.
6 //
7 // This file is part of CGAL (www.cgal.org)
8 //
9 // $URL: https://github.com/CGAL/cgal/blob/v5.3/Stream_support/include/CGAL/IO/VTK.h $
10 // $Id: VTK.h 4e519a3 2021-05-05T13:15:37+02:00 Sébastien Loriot
11 // SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
12 //
13 // Author(s)     : Maxime Gimeno
14 
15 #ifndef CGAL_IO_VTK_H
16 #define CGAL_IO_VTK_H
17 
18 #include <CGAL/IO/VTK/VTK_reader.h>
19 #include <CGAL/IO/VTK/VTK_writer.h>
20 
21 #include <CGAL/boost/graph/Named_function_parameters.h>
22 #include <CGAL/boost/graph/named_params_helper.h>
23 
24 #ifdef CGAL_USE_VTK
25 #include <vtkSmartPointer.h>
26 #include <vtkCommand.h>
27 #include <vtkCell.h>
28 #include <vtkXMLPolyDataReader.h>
29 #include <vtkPointSet.h>
30 #include <vtkPolyData.h>
31 #endif
32 
33 #if defined(CGAL_USE_VTK) || defined(DOXYGEN_RUNNING)
34 
35 #ifdef DOXYGEN_RUNNING
36 #define CGAL_BGL_NP_TEMPLATE_PARAMETERS NamedParameters
37 #define CGAL_BGL_NP_CLASS NamedParameters
38 #endif
39 
40 namespace CGAL {
41 namespace IO {
42 namespace internal {
43 
44 //append the content of poly_data to a soup.
45 template <typename PointRange, typename PolygonRange, typename NamedParameters>
vtkPointSet_to_polygon_soup(vtkPointSet * poly_data,PointRange & points,PolygonRange & polygons,const NamedParameters &)46 bool vtkPointSet_to_polygon_soup(vtkPointSet* poly_data,
47                                  PointRange& points,
48                                  PolygonRange& polygons,
49                                  const NamedParameters&)
50 {
51   vtkIdType nb_points = poly_data->GetNumberOfPoints();
52   vtkIdType nb_cells = poly_data->GetNumberOfCells();
53   polygons.reserve(nb_cells);
54   std::size_t initial_number_of_pts = points.size();
55 
56   // extract points
57   points.reserve(initial_number_of_pts+nb_points);
58   for(vtkIdType i=0; i<nb_points; ++i)
59   {
60     double coords[3];
61     poly_data->GetPoint(i, coords);
62     points.emplace_back(coords[0], coords[1], coords[2]);
63   }
64 
65   // extract cells
66   for(vtkIdType i=0; i<nb_cells; ++i)
67   {
68     int cell_type = poly_data->GetCellType(i);
69     if(cell_type != 5
70        && cell_type != 7
71        && cell_type != 9) // only supported cells are triangles, quads and polygons
72       continue;
73 
74     vtkCell* cell_ptr = poly_data->GetCell(i);
75 
76     vtkIdType nb_vertices = cell_ptr->GetNumberOfPoints();
77     if(nb_vertices < 3)
78       return false;
79 
80     std::vector<std::size_t> ids(nb_vertices);
81     for(vtkIdType k=0; k<nb_vertices; ++k)
82     {
83       vtkIdType id = cell_ptr->GetPointId(k);
84       ids[k] = id+initial_number_of_pts;
85     }
86     polygons.push_back(ids);
87   }
88 
89   return true;
90 }
91 } // namespace internal
92 
93 ////////////////////////////////////////////////////////////////////////////////////////////////////
94 ////////////////////////////////////////////////////////////////////////////////////////////////////
95 // Read
96 
97 template <typename PointRange, typename PolygonRange, typename NamedParameters>
read_VTP(const std::string & fname,PointRange & points,PolygonRange & polygons,const NamedParameters & np)98 bool read_VTP(const std::string& fname,
99               PointRange& points,
100               PolygonRange& polygons,
101               const NamedParameters& np)
102 {
103   std::ifstream test(fname);
104   if(!test.good())
105   {
106     std::cerr<<"File doesn't exist."<<std::endl;
107     return false;
108   }
109 
110   vtkSmartPointer<vtkPointSet> data;
111   vtkSmartPointer<internal::ErrorObserverVtk> obs =
112       vtkSmartPointer<internal::ErrorObserverVtk>::New();
113 
114   data = vtkPolyData::SafeDownCast(internal::read_vtk_file<vtkXMLPolyDataReader>(fname, obs)->GetOutput());
115   if (obs->GetError())
116     return false;
117 
118   return internal::vtkPointSet_to_polygon_soup(data, points, polygons, np);
119 }
120 
121 /*!
122  * \ingroup PkgStreamSupportIoFuncsVTP
123  *
124  * \brief reads the content of the input file into `points` and `polygons`, using the \ref IOStreamVTK.
125  *
126  * \attention The polygon soup is not cleared, and the data from the file are appended.
127  *
128  * \tparam PointRange a model of the concepts `RandomAccessContainer` and `BackInsertionSequence`
129  *                    whose value type is the point type
130  * \tparam PolygonRange a model of the concepts `SequenceContainer` and `BackInsertionSequence`
131  *                      whose `value_type` is itself a model of the concept `SequenceContainer`
132  *                      and `BackInsertionSequence` whose `value_type` is an unsigned integer type
133  *                      convertible to `std::size_t`
134  *
135  * \param fname the path to the input file
136  * \param points points of the soup of polygons
137  * \param polygons a range of polygons. Each element in it describes a polygon
138  *        using the indices of the points in `points`.
139  *
140  * \returns `true` if the reading was successful, `false` otherwise.
141  */
142 template <typename PointRange, typename PolygonRange>
read_VTP(const std::string & fname,PointRange & points,PolygonRange & polygons)143 bool read_VTP(const std::string& fname, PointRange& points, PolygonRange& polygons)
144 {
145   return read_VTP(fname, points, polygons, parameters::all_default());
146 }
147 
148 ////////////////////////////////////////////////////////////////////////////////////////////////////
149 ////////////////////////////////////////////////////////////////////////////////////////////////////
150 // Write
151 
152 namespace internal {
153 
154 // writes the polys appended data at the end of the .vtp file
155 template <typename PolygonRange>
write_soup_polys(std::ostream & os,const PolygonRange & polygons,const std::vector<std::size_t> & offsets,const std::vector<unsigned char> & cell_type)156 void write_soup_polys(std::ostream& os,
157                       const PolygonRange& polygons,
158                       const std::vector<std::size_t>& offsets,
159                       const std::vector<unsigned char>& cell_type)
160 {
161 
162   std::vector<std::size_t> connectivity_table;
163   std::size_t off = 0;
164   std::vector<std::size_t> cumul_offsets(offsets);
165 
166   for(size_t i = 0; i < polygons.size(); ++i)
167   {
168     const auto& poly = polygons[i];
169     off+=offsets[i];
170     cumul_offsets[i]=off;
171     for(const std::size_t& i : poly)
172       connectivity_table.push_back(i);
173   }
174 
175   write_vector<std::size_t>(os, connectivity_table);
176   write_vector<std::size_t>(os, cumul_offsets);
177   write_vector<unsigned char>(os, cell_type);
178 }
179 
180 template <typename PointRange>
write_soup_points_tag(std::ostream & os,const PointRange & points,bool binary,std::size_t & offset)181 void write_soup_points_tag(std::ostream& os,
182                            const PointRange& points,
183                            bool binary,
184                            std::size_t& offset)
185 {
186   typedef typename boost::range_value<PointRange>::type     Point;
187   typedef typename CGAL::Kernel_traits<Point>::Kernel       Gt;
188   typedef typename Gt::FT                                   FT;
189 
190   std::string format = binary ? "appended" : "ascii";
191   std::string type = (sizeof(FT) == 8) ? "Float64" : "Float32";
192 
193   os << "    <Points>\n"
194      << "      <DataArray type =\"" << type << "\" NumberOfComponents=\"3\" format=\""
195      << format;
196 
197   if(binary)
198   {
199     os << "\" offset=\"" << offset << "\"/>\n";
200     offset += 3 * points.size() * sizeof(FT) + sizeof(std::size_t);
201     // 3 coords per points + length of the encoded data (size_t)
202   }
203   else
204   {
205     os << "\">\n";
206     for(const Point& p : points)
207       os << oformat(p.x()) << " " << oformat(p.y()) << " " << oformat(p.z()) << " ";
208     os << "      </DataArray>\n";
209   }
210   os << "    </Points>\n";
211 }
212 
213 template <typename PolygonRange>
write_soup_polys_tag(std::ostream & os,const PolygonRange & polygons,bool binary,std::size_t & offset,std::vector<std::size_t> & size_map,std::vector<unsigned char> & cell_type)214 void write_soup_polys_tag(std::ostream& os,
215                           const PolygonRange& polygons,
216                           bool binary,
217                           std::size_t& offset,
218                           std::vector<std::size_t>& size_map,
219                           std::vector<unsigned char>& cell_type)
220 {
221   std::string formatattribute = binary ? " format=\"appended\"" : " format=\"ascii\"";
222 
223   std::string typeattribute;
224   switch(sizeof(std::size_t))
225   {
226   case 8: typeattribute = " type=\"UInt64\""; break;
227   case 4: typeattribute = " type=\"UInt32\""; break;
228   default: CGAL_error_msg("Unknown size of std::size_t");
229   }
230 
231   // Write connectivity table
232   os << "    <Polys>\n"
233      << "      <DataArray Name=\"connectivity\""
234      << formatattribute << typeattribute;
235 
236 
237   //fill a vector of sizes
238   size_map.resize(polygons.size());
239   cell_type.resize(polygons.size());
240   std::size_t total_size=0;
241   for(std::size_t i = 0; i < polygons.size(); ++i)
242   {
243     size_map[i] = polygons[i].size();
244     CGAL_assertion(size_map[i]>=3);
245     total_size +=size_map[i];
246   }
247 
248   // if binary output, just write the xml tag
249   if(binary)
250   {
251     os << " offset=\"" << offset << "\"/>\n";
252     offset += (total_size + 1) * sizeof(std::size_t);
253     // n indices (size_t) per triangle + length of the encoded data (size_t)
254   }
255   else
256   {
257     os << ">\n";
258 
259     for(const auto& poly : polygons)
260     {
261       for(const std::size_t& id : poly)
262         os << id << " ";
263     }
264     os << "      </DataArray>\n";
265   }
266 
267   // Write offsets
268   os << "      <DataArray Name=\"offsets\""
269      << formatattribute << typeattribute;
270 
271   if(binary) {  // if binary output, just write the xml tag
272     os << " offset=\"" << offset << "\"/>\n";
273     offset += (polygons.size() + 1) * sizeof(std::size_t);
274     // 1 offset (size_t) per triangle + length of the encoded data (size_t)
275   }
276   else
277   {
278     os << ">\n";
279     std::size_t polys_offset = 0;
280 
281     for(std::size_t i = 0; i < polygons.size(); ++i)
282     {
283       polys_offset += size_map[i];
284       os << polys_offset << " ";
285     }
286     os << "      </DataArray>\n";
287   }
288 
289   // Write cell type (triangle == 5)
290   os << "      <DataArray Name=\"types\""
291      << formatattribute << " type=\"UInt8\"";
292 
293   if(binary)
294   {
295     os << " offset=\"" << offset << "\"/>\n";
296     offset += polygons.size() + sizeof(std::size_t);
297     // 1 unsigned char per cell + length of the encoded data (size_t)
298   }
299   else
300   {
301     os << ">\n";
302     for(std::size_t i = 0; i< polygons.size(); ++i)
303     {
304       switch(size_map[i]){
305       case 3:
306         cell_type[i]=5;
307         os << "5 ";//triangle
308         break;
309       case 4:
310         cell_type[i]=7;
311         os << "7 ";//quad
312         break;
313       default:
314         cell_type[i]=9;
315         os << "9 ";//polygon
316         break;
317       }
318     }
319     os << "      </DataArray>\n";
320   }
321   os << "    </Polys>\n";
322 }
323 
324 // writes the points appended data at the end of the .vtp file
325 template <typename PointRange>
write_soup_polys_points(std::ostream & os,const PointRange & points)326 void write_soup_polys_points(std::ostream& os,
327                              const PointRange& points)
328 {
329   typedef typename boost::range_value<PointRange>::type     Point;
330   typedef typename CGAL::Kernel_traits<Point>::Kernel       Gt;
331   typedef typename Gt::FT                                   FT;
332 
333   std::vector<FT> coordinates;
334 
335   for(const Point& p : points)
336   {
337     coordinates.push_back(p.x());
338     coordinates.push_back(p.y());
339     coordinates.push_back(p.z());
340   }
341 
342   write_vector<FT>(os, coordinates);
343 }
344 
345 } // namespace internal
346 
347 /*!
348  * \ingroup PkgStreamSupportIoFuncsVTP
349  *
350  * \brief writes the content of `points` and `polygons` in `out`, using the \ref IOStreamVTK.
351  *
352  * \tparam PointRange a model of the concept `RandomAccessContainer` whose value type is the point type
353  * \tparam PolygonRange a model of the concept `SequenceContainer`
354  *                      whose `value_type` is itself a model of the concept `SequenceContainer`
355  *                      whose `value_type` is an unsigned integer type convertible to `std::size_t`
356  * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
357  *
358  * \param os the output stream
359  * \param points points of the soup of polygons
360  * \param polygons a range of polygons. Each element in it describes a polygon
361  *        using the indices of the points in `points`.
362  * \param np optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
363  *
364  * \cgalNamedParamsBegin
365  *   \cgalParamNBegin{use_binary_mode}
366  *     \cgalParamDescription{indicates whether data should be written in binary (`true`) or in ASCII (`false`)}
367  *     \cgalParamType{Boolean}
368  *     \cgalParamDefault{`true`}
369  *   \cgalParamNEnd
370  *
371  *   \cgalParamNBegin{stream_precision}
372  *     \cgalParamDescription{a parameter used to set the precision (i.e. how many digits are generated) of the output stream}
373  *     \cgalParamType{int}
374  *     \cgalParamDefault{`the precision of the stream `os``}
375  *   \cgalParamNEnd
376  * \cgalNamedParamsEnd
377  *
378  * \return `true` if the writing was successful, `false` otherwise.
379  */
380 template <typename PointRange,
381           typename PolygonRange,
382           typename CGAL_BGL_NP_TEMPLATE_PARAMETERS>
write_VTP(std::ostream & os,const PointRange & points,const PolygonRange & polygons,const CGAL_BGL_NP_CLASS & np)383 bool write_VTP(std::ostream& os,
384                const PointRange& points,
385                const PolygonRange& polygons,
386                const CGAL_BGL_NP_CLASS& np)
387 {
388   using parameters::get_parameter;
389   using parameters::choose_parameter;
390 
391   if(!os.good())
392     return false;
393 
394   set_stream_precision_from_NP(os, np);
395 
396   os << "<?xml version=\"1.0\"?>\n"
397      << "<VTKFile type=\"PolyData\" version=\"0.1\"";
398 
399 #ifdef CGAL_LITTLE_ENDIAN
400   os << " byte_order=\"LittleEndian\"";
401 #else // CGAL_BIG_ENDIAN
402   os << " byte_order=\"BigEndian\"";
403 #endif
404 
405   switch(sizeof(std::size_t))
406   {
407   case 4: os << " header_type=\"UInt32\""; break;
408   case 8: os << " header_type=\"UInt64\""; break;
409   default: CGAL_error_msg("Unknown size of std::size_t");
410   }
411 
412   os << ">\n"
413      << "  <PolyData>" << "\n";
414   os << "  <Piece NumberOfPoints=\"" << points.size()
415      << "\" NumberOfPolys=\"" << polygons.size() << "\">\n";
416 
417   std::size_t offset = 0;
418   const bool binary = choose_parameter(get_parameter(np, internal_np::use_binary_mode), true);
419   std::vector<std::size_t> size_map;
420   std::vector<unsigned char> cell_type;
421   internal::write_soup_points_tag(os, points, binary, offset);
422   internal::write_soup_polys_tag(os, polygons, binary, offset, size_map, cell_type);
423 
424   os << "   </Piece>\n"
425      << "  </PolyData>\n";
426   if(binary)
427   {
428     os << "<AppendedData encoding=\"raw\">\n_";
429     internal::write_soup_polys_points(os, points);
430     internal::write_soup_polys(os, polygons,size_map, cell_type);
431   }
432   os << "</VTKFile>" << std::endl;
433 }
434 
435 /// \cond SKIP_IN_MANUAL
436 
437 template <typename PointRange, typename PolygonRange>
write_VTP(std::ostream & os,const PointRange & points,const PolygonRange & polygons)438 bool write_VTP(std::ostream& os, const PointRange& points, const PolygonRange& polygons)
439 {
440   return write_VTP(os, points, polygons, parameters::all_default());
441 }
442 
443 /// \endcond
444 
445 /*!
446  * \ingroup PkgStreamSupportIoFuncsVTP
447  *
448  * \brief writes the content of `points` and `polygons` in a file named `fname`, using the \ref IOStreamVTK.
449  *
450  * \tparam PointRange a model of the concept `RandomAccessContainer` whose value type is the point type
451  * \tparam PolygonRange a model of the concept `SequenceContainer`
452  *                      whose `value_type` is itself a model of the concept `SequenceContainer`
453  *                      whose `value_type` is an unsigned integer type convertible to `std::size_t`
454  * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
455  *
456  * \param fname the path to the output file
457  * \param points points of the soup of polygons
458  * \param polygons a range of polygons. Each element in it describes a polygon
459  *        using the indices of the points in `points`.
460  * \param np optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
461  *
462  * \cgalNamedParamsBegin
463  *   \cgalParamNBegin{use_binary_mode}
464  *     \cgalParamDescription{indicates whether data should be written in binary (`true`) or in ASCII (`false`)}
465  *     \cgalParamType{Boolean}
466  *     \cgalParamDefault{`true`}
467  *   \cgalParamNEnd
468  *
469  *   \cgalParamNBegin{stream_precision}
470  *     \cgalParamDescription{a parameter used to set the precision (i.e. how many digits are generated) of the output stream}
471  *     \cgalParamType{int}
472  *     \cgalParamDefault{`6`}
473  *   \cgalParamNEnd
474  * \cgalNamedParamsEnd
475  *
476  * \return `true` if the writing was successful, `false` otherwise.
477  */
478 template <typename PointRange, typename PolygonRange, typename CGAL_BGL_NP_TEMPLATE_PARAMETERS>
write_VTP(const std::string & fname,const PointRange & points,const PolygonRange & polygons,const CGAL_BGL_NP_CLASS & np)479 bool write_VTP(const std::string& fname,
480                const PointRange& points,
481                const PolygonRange& polygons,
482                const CGAL_BGL_NP_CLASS& np)
483 {
484   const bool binary = CGAL::parameters::choose_parameter(CGAL::parameters::get_parameter(np, internal_np::use_binary_mode), true);
485   if(binary)
486   {
487     std::ofstream os(fname, std::ios::binary);
488     CGAL::IO::set_mode(os, CGAL::IO::BINARY);
489     return write_VTP(os, points, polygons, np);
490   }
491   else
492   {
493     std::ofstream os(fname);
494     CGAL::IO::set_mode(os, CGAL::IO::ASCII);
495     return write_VTP(os, points, polygons, np);
496   }
497 }
498 
499 /// \cond SKIP_IN_MANUAL
500 
501 template <typename PointRange, typename PolygonRange>
write_VTP(const std::string & fname,const PointRange & points,const PolygonRange & polygons)502 bool write_VTP(const std::string& fname, const PointRange& points, const PolygonRange& polygons)
503 {
504   return write_VTP(fname, points, polygons, parameters::all_default());
505 }
506 
507 /// \endcond
508 
509 } // namespace IO
510 
511 } // namespace CGAL
512 
513 #endif // defined(CGAL_USE_VTK) || defined(DOXYGEN_RUNNING)
514 
515 #endif // CGAL_IO_VTK_H
516