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-2026, 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
193 if (detail::numProperties<Ts...>() < (last - first))
194 {
195 const PlyPropertyConstIterator firstToSkip = first + detail::numProperties<Ts...>();
196
197 // In case any property that needs to be skipped is a list property, take
198 // the expensive code path. Otherwise, we can calculate the exact number
199 // of bytes to skip over.
200 if (std::any_of(firstToSkip, last, [](const PlyProperty &p) { return p.isList(); }))
201 {
202 for (std::size_t i{0}; i < element.size(); ++i)
203 {
204 dest = detail::align(readElement<Ts...>(dest, first, last), alignment);
205
206 auto curr = firstToSkip;
207 while (curr < last) this->skipProperty(*curr++);
208 }
209 }
210 else
211 {
212 // Note; even though the following may seem specific for binary parsing
213 // only, it is still useful in terms of an ASCII parser. That is, in case
214 // any bytes need to be skipped, the ASCII parser will just ignore the
215 // remainder of the line to read, and as such skip to the next element.
216 const std::size_t numBytesToSkip =
217 std::accumulate(firstToSkip, last, std::size_t{0}, [](std::size_t acc, const PlyProperty &p) {
218 return acc + sizeOf(p.isList() ? p.sizeType() : p.type());
219 });
220
221 for (std::size_t i{0}; i < element.size(); ++i)
222 {
223 dest = detail::align(readElement<Ts...>(dest, first, last), alignment);
224 this->skipProperties(numBytesToSkip);
225 }
226 }
227 }
228 else
229 {
230 for (std::size_t i{0}; i < element.size(); ++i)
231 {
232 dest = detail::align(readElement<Ts...>(dest, first, last), alignment);
233 }
234 }
235 }
236
237 template<typename T>
238 std::uint8_t *readElement(std::uint8_t *dest, PlyPropertyConstIterator first, PlyPropertyConstIterator last)
239 const
240 {
241 return first < last ? readProperty(dest, *first, reflect::Type<T>{})
242 : static_cast<std::uint8_t *>(detail::align(dest, alignof(T))) + sizeof(T);
243 }
244
245 template<typename T, typename U, typename... Ts>
246 std::uint8_t *readElement(std::uint8_t *dest, PlyPropertyConstIterator first, PlyPropertyConstIterator last) const
247 {
248 return readElement<U, Ts...>(readElement<T>(dest, first, last), first + detail::numProperties<T>(), last);
249 }
250
251 template<typename PlyT, typename PlySizeT, typename DestT>
252 std::uint8_t *readListProperty(std::uint8_t *dest, reflect::Type<std::vector<DestT>>) const
253 {
254 dest = static_cast<std::uint8_t *>(detail::align(dest, alignof(std::vector<DestT>)));
255 std::vector<DestT> &v = *reinterpret_cast<std::vector<DestT> *>(dest);
256
257 const PlySizeT size = this->template readNumber<PlySizeT>();
258 v.reserve(static_cast<std::size_t>(size));
259 for (PlySizeT i = 0; i < size; ++i) { v.push_back(static_cast<DestT>(this->template readNumber<PlyT>())); }
260
261 return dest + sizeof(std::vector<DestT>);
262 }
263
264 template<typename PlyT, typename PlySizeT, typename DestT, std::size_t N>
265 std::uint8_t *readListProperty(std::uint8_t *dest, reflect::Type<reflect::Array<DestT, N>>) const
266 {
267 static_assert(std::is_arithmetic<PlyT>::value, "unexpected PLY data type");
268
269 // TODO(ton): skip the number that defines the list in the PLY data, we
270 // expect it to be of length N; throw an exception here in case they do no match?
271 this->template skipNumber<PlySizeT>();
272 dest = static_cast<std::uint8_t *>(detail::align(dest, alignof(DestT)));
273 return this->template readNumbers<PlyT, DestT, N>(dest);
274 }
275
276 template<typename PlyT, typename TypeTag>
277 std::uint8_t *readListProperty(std::uint8_t *dest, const PlyProperty &property, TypeTag tag) const
278 {
279 switch (property.sizeType())
280 {
281 case PlyDataType::Char:
282 return readListProperty<PlyT, char>(dest, tag);
283 case PlyDataType::UChar:
284 return readListProperty<PlyT, unsigned char>(dest, tag);
285 case PlyDataType::Short:
286 return readListProperty<PlyT, short>(dest, tag);
287 case PlyDataType::UShort:
288 return readListProperty<PlyT, unsigned short>(dest, tag);
289 case PlyDataType::Int:
290 return readListProperty<PlyT, int>(dest, tag);
291 case PlyDataType::UInt:
292 return readListProperty<PlyT, unsigned int>(dest, tag);
293 case PlyDataType::Float:
294 return readListProperty<PlyT, float>(dest, tag);
295 case PlyDataType::Double:
296 return readListProperty<PlyT, double>(dest, tag);
297 }
298
299 return dest;
300 }
301
302 template<typename PlyT, typename DestT>
303 std::uint8_t *readProperty(std::uint8_t *dest, reflect::Type<DestT>) const
304 {
305 if constexpr (std::is_arithmetic_v<DestT>)
306 {
307 dest = static_cast<std::uint8_t *>(detail::align(dest, alignof(DestT)));
308 *reinterpret_cast<DestT *>(dest) = static_cast<DestT>(this->template readNumber<PlyT>());
309 return dest + sizeof(DestT);
310 }
311 else { return static_cast<std::uint8_t *>(detail::align(dest, alignof(DestT))) + sizeof(DestT); }
312 }
313
314 template<typename PlyT>
315 std::uint8_t *readProperty(std::uint8_t *dest, reflect::Type<reflect::Skip>) const
316 {
317 return dest;
318 }
319
320 template<typename PlyT, typename DestT>
321 std::uint8_t *readProperty(std::uint8_t *dest, reflect::Type<reflect::Stride<DestT>>) const
322 {
323 return static_cast<std::uint8_t *>(detail::align(dest, alignof(DestT))) + sizeof(DestT);
324 }
325
326 template<typename PlyT, typename DestT, std::size_t N>
327 std::uint8_t *readProperty(std::uint8_t *dest, reflect::Type<reflect::Pack<DestT, N>>) const
328 {
329 static_assert(std::is_arithmetic<PlyT>::value, "unexpected PLY data type");
330 dest = static_cast<std::uint8_t *>(detail::align(dest, alignof(DestT)));
331 return this->template readNumbers<PlyT, DestT, N>(dest);
332 }
333
334 template<typename TypeTag>
335 std::uint8_t *readProperty(std::uint8_t *dest, const PlyProperty &property, TypeTag tag) const
336 {
337 if constexpr (detail::isList<TypeTag>())
338 {
339 switch (property.type())
340 {
341 case PlyDataType::Char:
342 return readListProperty<char>(dest, property, tag);
343 case PlyDataType::UChar:
344 return readListProperty<unsigned char>(dest, property, tag);
345 case PlyDataType::Short:
346 return readListProperty<short>(dest, property, tag);
347 case PlyDataType::UShort:
348 return readListProperty<unsigned short>(dest, property, tag);
349 case PlyDataType::Int:
350 return readListProperty<int>(dest, property, tag);
351 case PlyDataType::UInt:
352 return readListProperty<unsigned int>(dest, property, tag);
353 case PlyDataType::Float:
354 return readListProperty<float>(dest, property, tag);
355 case PlyDataType::Double:
356 return readListProperty<double>(dest, property, tag);
357 }
358 }
359 else if constexpr (std::is_same_v<typename TypeTag::DestT, reflect::Skip>)
360 {
361 this->skipProperty(property);
362 }
363 else
364 {
365 switch (property.type())
366 {
367 case PlyDataType::Char:
368 return readProperty<char>(dest, tag);
369 case PlyDataType::UChar:
370 return readProperty<unsigned char>(dest, tag);
371 case PlyDataType::Short:
372 return readProperty<short>(dest, tag);
373 case PlyDataType::UShort:
374 return readProperty<unsigned short>(dest, tag);
375 case PlyDataType::Int:
376 return readProperty<int>(dest, tag);
377 case PlyDataType::UInt:
378 return readProperty<unsigned int>(dest, tag);
379 case PlyDataType::Float:
380 return readProperty<float>(dest, tag);
381 case PlyDataType::Double:
382 return readProperty<double>(dest, tag);
383 }
384 }
385
386 return dest;
387 }
388};
389
390}
391
392#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