PLYwoot
Header-only C++17 library for parsing and writing PLY files
Loading...
Searching...
No Matches
parser.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_PARSER_HPP
21#define PLYWOOT_PARSER_HPP
22
24
25#include "reflect.hpp"
26#include "std.hpp"
27#include "type_traits.hpp"
28#include "types.hpp"
29
30#include <cstdint>
31#include <numeric>
32
33namespace plywoot::detail {
34
52template<typename FormatParserPolicy>
53class Parser : private FormatParserPolicy
54{
55private:
56 template<typename... Ts>
57 struct MaybeMemcpyable
58 {
59 static constexpr bool value = !std::is_same<FormatParserPolicy, AsciiParserPolicy>::value &&
60 detail::isPacked<Ts...>() && detail::isTriviallyCopyable<Ts...>();
61 };
62
63public:
64 using FormatParserPolicy::FormatParserPolicy;
65
76 PlyElementData read(const PlyElement &element) const
77 {
78 PlyElementData result(element);
79
80 std::uint8_t *dest = result.data();
81 for (std::size_t i = 0; i < element.size(); ++i)
82 {
83 for (const PlyProperty &property : element.properties())
84 {
85 // In case of a list property, allocate a vector of the right type, and
86 // read the variable length list.
87 if (property.isList())
88 {
89 switch (property.type())
90 {
91 case PlyDataType::Char:
92 dest = readProperty(dest, property, reflect::Type<std::vector<char>>{});
93 break;
94 case PlyDataType::UChar:
95 dest = readProperty(dest, property, reflect::Type<std::vector<unsigned char>>{});
96 break;
97 case PlyDataType::Short:
98 dest = readProperty(dest, property, reflect::Type<std::vector<short>>{});
99 break;
100 case PlyDataType::UShort:
101 dest = readProperty(dest, property, reflect::Type<std::vector<unsigned short>>{});
102 break;
103 case PlyDataType::Int:
104 dest = readProperty(dest, property, reflect::Type<std::vector<int>>{});
105 break;
106 case PlyDataType::UInt:
107 dest = readProperty(dest, property, reflect::Type<std::vector<unsigned int>>{});
108 break;
109 case PlyDataType::Float:
110 dest = readProperty(dest, property, reflect::Type<std::vector<float>>{});
111 break;
112 case PlyDataType::Double:
113 dest = readProperty(dest, property, reflect::Type<std::vector<double>>{});
114 break;
115 }
116 }
117 else
118 {
119 switch (property.type())
120 {
121 case PlyDataType::Char:
122 dest = readProperty(dest, property, reflect::Type<char>{});
123 break;
124 case PlyDataType::UChar:
125 dest = readProperty(dest, property, reflect::Type<unsigned char>{});
126 break;
127 case PlyDataType::Short:
128 dest = readProperty(dest, property, reflect::Type<short>{});
129 break;
130 case PlyDataType::UShort:
131 dest = readProperty(dest, property, reflect::Type<unsigned short>{});
132 break;
133 case PlyDataType::Int:
134 dest = readProperty(dest, property, reflect::Type<int>{});
135 break;
136 case PlyDataType::UInt:
137 dest = readProperty(dest, property, reflect::Type<unsigned int>{});
138 break;
139 case PlyDataType::Float:
140 dest = readProperty(dest, property, reflect::Type<float>{});
141 break;
142 case PlyDataType::Double:
143 dest = readProperty(dest, property, reflect::Type<double>{});
144 break;
145 }
146 }
147 }
148
149 dest = detail::align(dest, result.alignment());
150 }
151
152 return result;
153 }
154
167 // TODO(ton): probably better to add another parameter 'size' to guard
168 // against overwriting the input buffer.
169 template<typename... Ts>
170 void read(const PlyElement &element, std::uint8_t *dest, std::size_t alignment) const
171 {
172 if constexpr (MaybeMemcpyable<Ts...>::value)
173 {
174 if (detail::isMemcpyable<Ts...>(element.properties().begin(), element.properties().end()))
175 {
176 this->template memcpy<Ts...>(dest, element);
177 return;
178 }
179 }
180
181 readElements<Ts...>(element, dest, alignment);
182 }
183
184 void skip(const PlyElement &element) const { this->skipElement(element); }
185
186private:
187 template<typename... Ts>
188 void readElements(const PlyElement &element, std::uint8_t *dest, std::size_t alignment) const
189 {
190 const PlyPropertyConstIterator first = element.properties().begin();
191 const PlyPropertyConstIterator last = element.properties().end();
192 const PlyPropertyConstIterator firstToSkip = first + detail::numProperties<Ts...>();
193
194 if (firstToSkip < last)
195 {
196 // In case any property that needs to be skipped is a list property, take
197 // the expensive code path. Otherwise, we can calculate the exact number
198 // of bytes to skip over.
199 if (std::any_of(firstToSkip, last, [](const PlyProperty &p) { return p.isList(); }))
200 {
201 for (std::size_t i{0}; i < element.size(); ++i)
202 {
203 dest = detail::align(readElement<Ts...>(dest, first, last), alignment);
204
205 auto curr = firstToSkip;
206 while (curr < last) this->skipProperty(*curr++);
207 }
208 }
209 else
210 {
211 // Note; even though the following may seem specific for binary parsing
212 // only, it is still useful in terms of an ASCII parser. That is, in case
213 // any bytes need to be skipped, the ASCII parser will just ignore the
214 // remainder of the line to read, and as such skip to the next element.
215 const std::size_t numBytesToSkip =
216 std::accumulate(firstToSkip, last, 0ul, [](std::size_t acc, const PlyProperty &p) {
217 return acc + sizeOf(p.isList() ? p.sizeType() : p.type());
218 });
219
220 for (std::size_t i{0}; i < element.size(); ++i)
221 {
222 dest = detail::align(readElement<Ts...>(dest, first, last), alignment);
223 this->skipProperties(numBytesToSkip);
224 }
225 }
226 }
227 else
228 {
229 for (std::size_t i{0}; i < element.size(); ++i)
230 {
231 dest = detail::align(readElement<Ts...>(dest, first, last), alignment);
232 }
233 }
234 }
235
236 template<typename T>
237 std::uint8_t *readElement(std::uint8_t *dest, PlyPropertyConstIterator first, PlyPropertyConstIterator last)
238 const
239 {
240 return first < last ? readProperty(dest, *first, reflect::Type<T>{})
241 : static_cast<std::uint8_t *>(detail::align(dest, alignof(T))) + sizeof(T);
242 }
243
244 template<typename T, typename U, typename... Ts>
245 std::uint8_t *readElement(std::uint8_t *dest, PlyPropertyConstIterator first, PlyPropertyConstIterator last) const
246 {
247 return readElement<U, Ts...>(readElement<T>(dest, first, last), first + detail::numProperties<T>(), last);
248 }
249
250 template<typename PlyT, typename PlySizeT, typename DestT>
251 std::uint8_t *readListProperty(std::uint8_t *dest, reflect::Type<std::vector<DestT>>) const
252 {
253 dest = static_cast<std::uint8_t *>(detail::align(dest, alignof(std::vector<DestT>)));
254 std::vector<DestT> &v = *reinterpret_cast<std::vector<DestT> *>(dest);
255
256 const PlySizeT size = this->template readNumber<PlySizeT>();
257 v.reserve(size);
258 for (PlySizeT i = 0; i < size; ++i) { v.push_back(this->template readNumber<PlyT>()); }
259
260 return dest + sizeof(std::vector<DestT>);
261 }
262
263 template<typename PlyT, typename PlySizeT, typename DestT, std::size_t N>
264 std::uint8_t *readListProperty(std::uint8_t *dest, reflect::Type<reflect::Array<DestT, N>>) const
265 {
266 static_assert(std::is_arithmetic<PlyT>::value, "unexpected PLY data type");
267
268 // TODO(ton): skip the number that defines the list in the PLY data, we
269 // expect it to be of length N; throw an exception here in case they do no match?
270 this->template skipNumber<PlySizeT>();
271 dest = static_cast<std::uint8_t *>(detail::align(dest, alignof(DestT)));
272 return this->template readNumbers<PlyT, DestT, N>(dest);
273 }
274
275 template<typename PlyT, typename TypeTag>
276 std::uint8_t *readListProperty(std::uint8_t *dest, const PlyProperty &property, TypeTag tag) const
277 {
278 switch (property.sizeType())
279 {
280 case PlyDataType::Char:
281 return readListProperty<PlyT, char>(dest, tag);
282 case PlyDataType::UChar:
283 return readListProperty<PlyT, unsigned char>(dest, tag);
284 case PlyDataType::Short:
285 return readListProperty<PlyT, short>(dest, tag);
286 case PlyDataType::UShort:
287 return readListProperty<PlyT, unsigned short>(dest, tag);
288 case PlyDataType::Int:
289 return readListProperty<PlyT, int>(dest, tag);
290 case PlyDataType::UInt:
291 return readListProperty<PlyT, unsigned int>(dest, tag);
292 case PlyDataType::Float:
293 return readListProperty<PlyT, float>(dest, tag);
294 case PlyDataType::Double:
295 return readListProperty<PlyT, double>(dest, tag);
296 }
297
298 return dest;
299 }
300
301 template<typename PlyT, typename DestT>
302 std::uint8_t *readProperty(std::uint8_t *dest, reflect::Type<DestT>) const
303 {
304 if constexpr (std::is_arithmetic_v<DestT>)
305 {
306 dest = static_cast<std::uint8_t *>(detail::align(dest, alignof(DestT)));
307 *reinterpret_cast<DestT *>(dest) = this->template readNumber<PlyT>();
308 return dest + sizeof(DestT);
309 }
310 else { return static_cast<std::uint8_t *>(detail::align(dest, alignof(DestT))) + sizeof(DestT); }
311 }
312
313 template<typename PlyT>
314 std::uint8_t *readProperty(std::uint8_t *dest, reflect::Type<reflect::Skip>) const
315 {
316 return dest;
317 }
318
319 template<typename PlyT, typename DestT>
320 std::uint8_t *readProperty(std::uint8_t *dest, reflect::Type<reflect::Stride<DestT>>) const
321 {
322 return static_cast<std::uint8_t *>(detail::align(dest, alignof(DestT))) + sizeof(DestT);
323 }
324
325 template<typename PlyT, typename DestT, std::size_t N>
326 std::uint8_t *readProperty(std::uint8_t *dest, reflect::Type<reflect::Pack<DestT, N>>) const
327 {
328 static_assert(std::is_arithmetic<PlyT>::value, "unexpected PLY data type");
329 dest = static_cast<std::uint8_t *>(detail::align(dest, alignof(DestT)));
330 return this->template readNumbers<PlyT, DestT, N>(dest);
331 }
332
333 template<typename TypeTag>
334 std::uint8_t *readProperty(std::uint8_t *dest, const PlyProperty &property, TypeTag tag) const
335 {
336 if constexpr (detail::isList<TypeTag>())
337 {
338 switch (property.type())
339 {
340 case PlyDataType::Char:
341 return readListProperty<char>(dest, property, tag);
342 case PlyDataType::UChar:
343 return readListProperty<unsigned char>(dest, property, tag);
344 case PlyDataType::Short:
345 return readListProperty<short>(dest, property, tag);
346 case PlyDataType::UShort:
347 return readListProperty<unsigned short>(dest, property, tag);
348 case PlyDataType::Int:
349 return readListProperty<int>(dest, property, tag);
350 case PlyDataType::UInt:
351 return readListProperty<unsigned int>(dest, property, tag);
352 case PlyDataType::Float:
353 return readListProperty<float>(dest, property, tag);
354 case PlyDataType::Double:
355 return readListProperty<double>(dest, property, tag);
356 }
357 }
358 else if constexpr (std::is_same_v<typename TypeTag::DestT, reflect::Skip>)
359 {
360 this->skipProperty(property);
361 }
362 else
363 {
364 switch (property.type())
365 {
366 case PlyDataType::Char:
367 return readProperty<char>(dest, tag);
368 case PlyDataType::UChar:
369 return readProperty<unsigned char>(dest, tag);
370 case PlyDataType::Short:
371 return readProperty<short>(dest, tag);
372 case PlyDataType::UShort:
373 return readProperty<unsigned short>(dest, tag);
374 case PlyDataType::Int:
375 return readProperty<int>(dest, tag);
376 case PlyDataType::UInt:
377 return readProperty<unsigned int>(dest, tag);
378 case PlyDataType::Float:
379 return readProperty<float>(dest, tag);
380 case PlyDataType::Double:
381 return readProperty<double>(dest, tag);
382 }
383 }
384
385 return dest;
386 }
387};
388
389}
390
391#endif
constexpr Ptr align(Ptr ptr, std::size_t alignment)
Definition std.hpp:50
constexpr std::size_t sizeOf()
std::vector< PlyProperty >::const_iterator PlyPropertyConstIterator
Definition types.hpp:145