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