PLYwoot
Header-only C++17 library for parsing and writing PLY files
Loading...
Searching...
No Matches
plywoot.hpp
Go to the documentation of this file.
1/*
2 This file is part of PLYwoot, a header-only PLY parser.
3
4 Copyright (C) 2023-2025, Ton van den Heuvel
5
6 PLYwoot is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#ifndef PLYWOOT_HPP
21#define PLYWOOT_HPP
22
24
31#include "plywoot/parser.hpp"
33#include "plywoot/reflect.hpp"
35#include "plywoot/types.hpp"
36#include "plywoot/writer.hpp"
38
39#include <algorithm>
40#include <cstdint>
41#include <functional>
42#include <istream>
43#include <optional>
44#include <ostream>
45#include <string>
46#include <utility>
47#include <vector>
48
49namespace plywoot {
50
53{
54public:
59 IStream(std::istream &is) : IStream{is, detail::HeaderParser{is}} {}
60
64 const std::vector<Comment> &comments() const { return comments_; }
65
69 const std::vector<PlyElement> &elements() const { return elements_; }
70
81 std::optional<PlyElement> element(const std::string &name) const
82 {
83 const auto it = std::find_if(
84 elements_.begin(), elements_.end(), [&](const PlyElement &e) { return e.name() == name; });
85 return it != elements_.end() ? std::optional(*it) : std::nullopt;
86 }
87
91 PlyFormat format() const { return format_; }
92
100 bool find(const std::string &elementName) const
101 {
102 while (currElement_ != elements_.end() && currElement_->name() != elementName) { skipElement(); }
103 return currElement_ != elements_.end();
104 }
105
110 PlyElement element() const { return hasElement() ? *currElement_ : PlyElement{}; }
115 bool hasElement() const { return currElement_ != elements_.end(); }
116
126 PlyElementData readElement() const { return parser_.read(*currElement_++); }
127
143 // TODO(ton): probably better to add another parameter 'size' to guard
144 template<typename T, typename Layout>
145 void readElement(T *dest) const
146 {
147 parser_.read<Layout>(*currElement_++, reinterpret_cast<std::uint8_t *>(dest), alignof(T));
148 }
149
160 // TODO(ton): probably better to add another parameter 'size' to guard
161 template<typename T, typename Layout>
162 std::vector<T> readElement() const
163 {
164 std::vector<T> result(currElement_->size());
165 parser_.read<Layout>(*currElement_++, reinterpret_cast<std::uint8_t *>(result.data()), alignof(T));
166 return result;
167 }
168
170 void skipElement() const { parser_.skip(*currElement_++); }
171
172private:
177 IStream(std::istream &is, const detail::HeaderParser &headerParser)
178 : parser_{is, headerParser.format()},
179 comments_{headerParser.comments()},
180 elements_{headerParser.elements()},
181 format_{headerParser.format()},
182 currElement_{elements_.begin()}
183 {
184 }
185
188 detail::ParserVariant parser_;
189
191 std::vector<Comment> comments_;
193 std::vector<PlyElement> elements_;
195 PlyFormat format_;
196
198 mutable std::vector<PlyElement>::const_iterator currElement_;
199};
200
204{
205public:
209 OStream(PlyFormat format) : format_{format} {}
219 OStream(PlyFormat format, std::vector<Comment> comments) : format_{format}, comments_{std::move(comments)}
220 {
221 // Ensure comments are sorted on line number (ascending).
222 std::stable_sort(comments_.begin(), comments_.end(), [](const Comment &x, const Comment &y) {
223 return x.line < y.line;
224 });
225 }
226
233 template<typename... Ts>
234 void add(const PlyElement &element, const reflect::Layout<Ts...> layout)
235 {
236 // Note; create a copy of the element that specifies as size the number of
237 // items in the input layout.
238 PlyElement layoutElement{element.name(), layout.size(), element.properties()};
239
240 elementWriteClosures_.emplace_back(
241 std::move(layoutElement),
242 [layout](detail::WriterVariant &writer, const PlyElement &e) { writer.write(e, layout); });
243 }
244
250 void add(const PlyElementData &elementData)
251 {
252 // TODO(ton): ideally, move capture element data in the lambda below, but it
253 // runs into the issue that the `std::function` instance needs to be
254 // copyable.
255 const std::uint8_t *src = elementData.data();
256 const std::size_t alignment = elementData.alignment();
257
258 elementWriteClosures_.emplace_back(
259 elementData.element(), [src, alignment](detail::WriterVariant &writer, const PlyElement &e) {
260 writer.write(e, src, alignment);
261 });
262 }
263
268 void write(std::ostream &os) const
269 {
270 writeHeader(os);
271
272 detail::WriterVariant writer{os, format_};
273 for (const auto &elementClosurePair : elementWriteClosures_)
274 {
275 const PlyElement &element{elementClosurePair.first};
276 const auto &writeFn = elementClosurePair.second;
277 writeFn(writer, element);
278 }
279 }
280
281private:
286 void writeHeader(std::ostream &os) const
287 {
288 os << "ply\n";
289
290 switch (format_)
291 {
292 case PlyFormat::Ascii:
293 os << "format ascii 1.0\n";
294 break;
295 case PlyFormat::BinaryBigEndian:
296 os << "format binary_big_endian 1.0\n";
297 break;
298 case PlyFormat::BinaryLittleEndian:
299 os << "format binary_little_endian 1.0\n";
300 break;
301 }
302
303 // Maintain line number to be able to serialize comments at the right
304 // location in the header. Comments may only occur after the 'ply' magic
305 // 'number', and the format specification. Ideally this state should be
306 // limited to the scope of the closure, but that is not supported in C++11.
307 std::uint32_t line = 2;
308 auto first = comments_.begin();
309 const auto last = comments_.end();
310 const auto maybeWriteComments = [&]() {
311 while (first != last && first->line == line++)
312 {
313 if (first->text.empty()) { os << "comment\n"; }
314 else { os << "comment " << first->text << '\n'; }
315 ++first;
316 }
317 };
318
319 for (const auto &elementClosurePair : elementWriteClosures_)
320 {
321 maybeWriteComments();
322 const PlyElement &element{elementClosurePair.first};
323 os << "element " << element.name() << ' ' << element.size() << '\n';
324
325 for (const PlyProperty &property : element.properties())
326 {
327 maybeWriteComments();
328 if (property.isList())
329 {
330 os << "property list " << property.sizeType() << ' ' << property.type() << ' ' << property.name()
331 << '\n';
332 }
333 else { os << "property " << property.type() << ' ' << property.name() << '\n'; }
334 }
335 }
336
337 maybeWriteComments();
338 os << "end_header\n";
339 }
340
341 using ElementWriteClosure = std::function<void(detail::WriterVariant &, const PlyElement &)>;
342
344 std::vector<std::pair<PlyElement, ElementWriteClosure>> elementWriteClosures_;
346 PlyFormat format_;
349 std::vector<Comment> comments_;
350};
351
358inline void convert(std::istream &is, std::ostream &os, PlyFormat format)
359{
360 IStream plyIs{is};
361 OStream plyOs{format};
362
363 // Note; preserve element data until the write() function below finishes.
364 std::vector<PlyElementData> elementsData;
365
366 while (plyIs.hasElement())
367 {
368 elementsData.emplace_back(plyIs.readElement());
369 plyOs.add(elementsData.back());
370 }
371
372 plyOs.write(os);
373}
374
375}
376
377#endif
Represents an input PLY data stream that can be queried for data.
Definition plywoot.hpp:53
PlyElementData readElement() const
Definition plywoot.hpp:126
const std::vector< Comment > & comments() const
Definition plywoot.hpp:64
PlyFormat format() const
Definition plywoot.hpp:91
std::vector< T > readElement() const
Definition plywoot.hpp:162
IStream(std::istream &is)
Definition plywoot.hpp:59
bool hasElement() const
Definition plywoot.hpp:115
void readElement(T *dest) const
Definition plywoot.hpp:145
PlyElement element() const
Definition plywoot.hpp:110
std::optional< PlyElement > element(const std::string &name) const
Definition plywoot.hpp:81
bool find(const std::string &elementName) const
Definition plywoot.hpp:100
void skipElement() const
Skips the current element.
Definition plywoot.hpp:170
const std::vector< PlyElement > & elements() const
Definition plywoot.hpp:69
OStream(PlyFormat format)
Definition plywoot.hpp:209
OStream(PlyFormat format, std::vector< Comment > comments)
Definition plywoot.hpp:219
void add(const PlyElementData &elementData)
Definition plywoot.hpp:250
void add(const PlyElement &element, const reflect::Layout< Ts... > layout)
Definition plywoot.hpp:234
void write(std::ostream &os) const
Definition plywoot.hpp:268
const PlyElement & element() const
std::uint8_t * data() const
std::size_t alignment() const
std::size_t size() const
Definition reflect.hpp:124
void convert(std::istream &is, std::ostream &os, PlyFormat format)
Definition plywoot.hpp:358
const std::vector< PlyProperty > & properties() const
Definition types.hpp:181
const std::string & name() const
Definition types.hpp:173
PlyFormat
Definition types.hpp:72