Visual Computing Library
All Classes Functions Variables Typedefs Enumerations Friends Modules Pages Concepts
face.h
1/*****************************************************************************
2 * VCLib *
3 * Visual Computing Library *
4 * *
5 * Copyright(C) 2021-2025 *
6 * Visual Computing Lab *
7 * ISTI - Italian National Research Council *
8 * *
9 * All rights reserved. *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the Mozilla Public License Version 2.0 as published *
13 * by the Mozilla Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * Mozilla Public License Version 2.0 *
20 * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. *
21 ****************************************************************************/
22
23#ifndef VCL_LOAD_SAVE_PLY_DETAIL_FACE_H
24#define VCL_LOAD_SAVE_PLY_DETAIL_FACE_H
25
26#include "header.h"
27
28#include <vclib/algorithms/mesh/face_topology.h>
29#include <vclib/exceptions/io.h>
30#include <vclib/io/file_type.h>
31#include <vclib/io/read.h>
32#include <vclib/io/write.h>
33#include <vclib/mesh/requirements.h>
34#include <vclib/misc/tokenizer.h>
35
36namespace vcl::detail {
37
38template<FaceMeshConcept MeshType, FaceConcept FaceType>
39void writePlyFaceIndices(
40 std::ostream& file,
41 PlyProperty p,
42 const MeshType& m,
43 const std::vector<uint>& vIndices,
44 const FaceType& f,
45 FileType format)
46{
47 using VertexType = MeshType::VertexType;
48
49 uint fsize = f.vertexNumber();
50 io::writeProperty(file, fsize, p.listSizeType, format);
51 for (const VertexType* v : f.vertices()) {
52 io::writeProperty(file, vIndices[m.index(v)], p.type, format);
53 }
54}
55
56template<FaceMeshConcept MeshType, FaceConcept FaceType>
57void setPlyFaceIndices(FaceType& f, MeshType& m, const std::vector<uint>& vids)
58{
59 bool splitFace = false;
60 // we have a polygonal mesh
61 if constexpr (FaceType::VERTEX_NUMBER < 0) {
62 // need to resize the face to the right number of verts
63 f.resizeVertices(vids.size());
64 }
65 else if (FaceType::VERTEX_NUMBER != vids.size()) {
66 // we have faces with static sizes (triangles), but we are loading faces
67 // with number of verts > 3. Need to split the face we are loading in n
68 // faces!
69 splitFace = true;
70 }
71
72 if (!splitFace) { // classic load, no split needed
73 uint i = 0;
74 for (uint i = 0; i < f.vertexNumber(); ++i) {
75 if (vids[i] >= m.vertexNumber()) {
76 throw MalformedFileException(
77 "Bad vertex index for face " + std::to_string(f.index()));
78 }
79 f.setVertex(i, vids[i]);
80 }
81 }
82 else { // split needed
83 addTriangleFacesFromPolygon(m, f, vids);
84 }
85}
86
87template<FaceMeshConcept MeshType, FaceConcept FaceType, typename Scalar>
88void setPlyFaceWedgeTexCoords(
89 FaceType& f,
90 MeshType& m,
91 const std::vector<uint>& vids,
92 const std::vector<std::pair<Scalar, Scalar>>& wedges)
93{
94 bool splitFace = false;
95 if (FaceType::VERTEX_NUMBER > 0 && FaceType::VERTEX_NUMBER != wedges.size())
96 splitFace = true;
97 if (!splitFace) {
98 for (uint i = 0; i < wedges.size(); ++i) {
99 f.wedgeTexCoord(i).u() = wedges[i].first;
100 f.wedgeTexCoord(i).v() = wedges[i].second;
101 }
102 }
103 else { // the face has been splitted
104 // for each triangle generated by the polygon
105 for (uint ff = m.index(f); ff < m.faceNumber(); ++ff) {
106 // for each vertex/wedge of the triangle
107 for (uint i = 0; i < m.face(ff).vertexNumber(); ++i) {
108 // get the position of this vertex in the original polygon
109 uint vid = m.index(m.face(ff).vertex(i));
110 auto it = std::find(vids.begin(), vids.end(), vid);
111 if (it == vids.end()) { // should never happen
112 // if this happens, it means that this triangle was not
113 // generated by the polygon
114 throw MalformedFileException(
115 "Bad vertex index for face " + std::to_string(ff));
116 }
117 // p is the position of the vertex in the polygon
118 uint p = it - vids.begin();
119 m.face(ff).wedgeTexCoord(i).u() = wedges[p].first;
120 m.face(ff).wedgeTexCoord(i).v() = wedges[p].second;
121 }
122 }
123 }
124}
125
126template<FaceMeshConcept MeshType, FaceConcept FaceType, typename Stream>
127void readPlyFaceProperty(
128 Stream& file,
129 MeshType& mesh,
130 FaceType& f,
131 PlyProperty p,
132 MeshInfo& loadedInfo,
133 std::endian end = std::endian::little)
134{
135 bool hasBeenRead = false;
136 std::vector<uint> vids; // contains the vertex ids of the actual face
137 if (p.name == ply::vertex_indices) { // loading vertex indices
138 uint fSize = io::readPrimitiveType<uint>(file, p.listSizeType, end);
139 loadedInfo.updateMeshType(fSize);
140 vids.resize(fSize);
141 for (uint i = 0; i < fSize; ++i) {
142 vids[i] = io::readPrimitiveType<size_t>(file, p.type, end);
143 }
144 hasBeenRead = true;
145 // will manage the case of loading a polygon in a triangle mesh
146 setPlyFaceIndices(f, mesh, vids);
147 }
148 if (p.name == ply::texcoord) { // loading wedge texcoords
149 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
151 using Scalar = FaceType::WedgeTexCoordType::ScalarType;
152 uint uvSize =
153 io::readPrimitiveType<uint>(file, p.listSizeType, end);
154 uint fSize = uvSize / 2;
155 std::vector<std::pair<Scalar, Scalar>> wedges(fSize);
156 for (uint i = 0; i < fSize; ++i) {
157 Scalar u = io::readPrimitiveType<Scalar>(file, p.type, end);
158 Scalar v = io::readPrimitiveType<Scalar>(file, p.type, end);
159 wedges[i].first = u;
160 wedges[i].second = v;
161 }
162 hasBeenRead = true;
163 setPlyFaceWedgeTexCoords(f, mesh, vids, wedges);
164 }
165 }
166 }
167 // loading texture id associated to ALL the wedges
168 if (p.name == ply::texnumber) {
169 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
171 uint n = io::readPrimitiveType<uint>(file, p.type, end);
172 hasBeenRead = true;
173 // in case the loaded polygon has been triangulated in the last
174 // n triangles of mesh
175 for (uint ff = mesh.index(f); ff < mesh.faceNumber(); ++ff) {
176 mesh.face(ff).textureIndex() = n;
177 }
178 }
179 }
180 }
181 // loading one of the normal components
182 if (p.name >= ply::nx && p.name <= ply::nz) {
183 if constexpr (HasPerFaceNormal<MeshType>) {
184 if (isPerFaceNormalAvailable(mesh)) {
185 using Scalar = FaceType::NormalType::ScalarType;
186 int a = p.name - ply::nx;
187 Scalar n = io::readPrimitiveType<Scalar>(file, p.type, end);
188 hasBeenRead = true;
189 // in case the loaded polygon has been triangulated in the last
190 // n triangles of mesh
191 for (uint ff = mesh.index(f); ff < mesh.faceNumber(); ++ff) {
192 mesh.face(ff).normal()[a] = n;
193 }
194 }
195 }
196 }
197 // loading one of the color components
198 if (p.name >= ply::red && p.name <= ply::alpha) {
199 if constexpr (HasPerFaceColor<MeshType>) {
200 if (isPerFaceColorAvailable(mesh)) {
201 int a = p.name - ply::red;
202 unsigned char c =
203 io::readPrimitiveType<unsigned char>(file, p.type, end);
204 hasBeenRead = true;
205 // in case the loaded polygon has been triangulated in the last
206 // n triangles of mesh
207 for (uint ff = mesh.index(f); ff < mesh.faceNumber(); ++ff) {
208 mesh.face(ff).color()[a] = c;
209 }
210 }
211 }
212 }
213 if (p.name == ply::quality) { // loading the quality component
214 if constexpr (HasPerFaceQuality<MeshType>) {
215 using QualityType = FaceType::QualityType;
216 if (isPerFaceQualityAvailable(mesh)) {
217 QualityType s =
218 io::readPrimitiveType<QualityType>(file, p.type, end);
219 hasBeenRead = true;
220 // in case the loaded polygon has been triangulated in the last
221 // n triangles of mesh
222 for (uint ff = mesh.index(f); ff < mesh.faceNumber(); ++ff) {
223 mesh.face(ff).quality() = s;
224 }
225 }
226 }
227 }
228 if (p.name == ply::unknown) {
229 if constexpr (HasPerFaceCustomComponents<MeshType>) {
230 if (mesh.hasPerFaceCustomComponent(p.unknownPropertyName)) {
231 io::readCustomComponent(
232 file, f, p.unknownPropertyName, p.type, end);
233 hasBeenRead = true;
234 }
235 }
236 }
237 // if nothing has been read, it means that there is some data we don't know
238 // we still need to read and discard what we read
239 if (!hasBeenRead) {
240 if (p.list) {
241 uint s = io::readPrimitiveType<int>(file, p.listSizeType, end);
242 for (uint i = 0; i < s; ++i)
243 io::readPrimitiveType<int>(file, p.type, end);
244 }
245 else {
246 io::readPrimitiveType<int>(file, p.type, end);
247 }
248 }
249}
250
251template<FaceConcept FaceType, MeshConcept MeshType>
252void readPlyFaceTxt(
253 std::istream& file,
254 FaceType& f,
255 MeshType& mesh,
256 MeshInfo& loadedInfo,
257 const std::list<PlyProperty>& faceProperties)
258{
259 Tokenizer spaceTokenizer = readAndTokenizeNextNonEmptyLine(file);
260 Tokenizer::iterator token = spaceTokenizer.begin();
261 for (const PlyProperty& p : faceProperties) {
262 if (token == spaceTokenizer.end()) {
263 throw MalformedFileException("Unexpected end of line.");
264 }
265 readPlyFaceProperty(token, mesh, f, p, loadedInfo);
266 }
267}
268
269template<FaceConcept FaceType, MeshConcept MeshType>
270void readPlyFaceBin(
271 std::istream& file,
272 FaceType& f,
273 MeshType& mesh,
274 MeshInfo& loadedInfo,
275 const std::list<PlyProperty>& faceProperties,
276 std::endian end)
277{
278 for (const PlyProperty& p : faceProperties) {
279 readPlyFaceProperty(file, mesh, f, p, loadedInfo, end);
280 }
281}
282
283template<FaceMeshConcept MeshType>
284void writePlyFaces(
285 std::ostream& file,
286 const PlyHeader& header,
287 const MeshType& mesh)
288{
289 using FaceType = MeshType::FaceType;
290
291 FileType format;
292 if (header.format() == ply::ASCII) {
293 format.isBinary = false;
294 }
295 else if (header.format() == ply::BINARY_BIG_ENDIAN) {
296 format.endian = std::endian::big;
297 }
298
299 // indices of vertices that do not consider deleted vertices
300 std::vector<uint> vIndices = mesh.vertexCompactIndices();
301
302 for (const FaceType& f : mesh.faces()) {
303 for (const PlyProperty& p : header.faceProperties()) {
304 bool hasBeenWritten = false;
305 if (p.name == ply::vertex_indices) {
306 detail::writePlyFaceIndices(file, p, mesh, vIndices, f, format);
307 hasBeenWritten = true;
308 }
309 if (p.name >= ply::nx && p.name <= ply::nz) {
310 if constexpr (HasPerFaceNormal<MeshType>) {
311 io::writeProperty(
312 file, f.normal()[p.name - ply::nx], p.type, format);
313 hasBeenWritten = true;
314 }
315 }
316 if (p.name >= ply::red && p.name <= ply::alpha) {
317 if constexpr (HasPerFaceColor<MeshType>) {
318 io::writeProperty(
319 file, f.color()[p.name - ply::red], p.type, format);
320 hasBeenWritten = true;
321 }
322 }
323 if (p.name == ply::quality) {
324 if constexpr (HasPerFaceQuality<MeshType>) {
325 io::writeProperty(file, f.quality(), p.type, format);
326 hasBeenWritten = true;
327 }
328 }
329 if (p.name == ply::texcoord) {
330 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
331 io::writeProperty(
332 file, f.vertexNumber() * 2, p.listSizeType, format);
333 for (const auto& tc : f.wedgeTexCoords()) {
334 io::writeProperty(file, tc.u(), p.type, format);
335 io::writeProperty(file, tc.v(), p.type, format);
336 }
337 hasBeenWritten = true;
338 }
339 }
340 if (p.name == ply::texnumber) {
341 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
342 io::writeProperty(file, f.textureIndex(), p.type, format);
343 hasBeenWritten = true;
344 }
345 }
346 if (p.name == ply::unknown) {
347 if constexpr (HasPerFaceCustomComponents<MeshType>) {
348 if (mesh.hasPerFaceCustomComponent(p.unknownPropertyName)) {
349 io::writeCustomComponent(
350 file, f, p.unknownPropertyName, p.type, format);
351 hasBeenWritten = true;
352 }
353 }
354 }
355 if (!hasBeenWritten) {
356 // be sure to write something if the header declares some
357 // property that is not in the mesh
358 io::writeProperty(file, 0, p.type, format);
359 }
360 }
361 if (!format.isBinary)
362 file << std::endl;
363 }
364}
365
366template<FaceMeshConcept MeshType, LoggerConcept LogType>
367void readPlyFaces(
368 std::istream& file,
369 const PlyHeader& header,
370 MeshType& mesh,
371 MeshInfo& loadedInfo,
372 LogType& log)
373{
374 using FaceType = MeshType::FaceType;
375 mesh.reserveFaces(header.numberFaces());
376
377 log.startProgress("Reading faces", header.numberFaces());
378
379 for (uint fid = 0; fid < header.numberFaces(); ++fid) {
380 uint ffid = mesh.addFace();
381 FaceType& f = mesh.face(ffid);
382 if (header.format() == ply::ASCII) {
383 detail::readPlyFaceTxt(
384 file, f, mesh, loadedInfo, header.faceProperties());
385 }
386 else {
387 std::endian end = header.format() == ply::BINARY_BIG_ENDIAN ?
388 std::endian::big :
389 std::endian::little;
390 detail::readPlyFaceBin(
391 file, f, mesh, loadedInfo, header.faceProperties(), end);
392 }
393
394 log.progress(fid);
395 }
396
397 log.endProgress();
398}
399
400} // namespace vcl::detail
401
402#endif // VCL_LOAD_SAVE_PLY_DETAIL_FACE_H
bool isPerFaceWedgeTexCoordsAvailable(const MeshType &m)
Returns true if the WedgeTexCoords component is available (enabled) in the Face element of the input ...
Definition face_requirements.h:571
bool isPerFaceNormalAvailable(const MeshType &m)
Returns true if the Normal component is available (enabled) in the Face element of the input mesh m.
Definition face_requirements.h:330
bool isPerFaceQualityAvailable(const MeshType &m)
Returns true if the Quality component is available (enabled) in the Face element of the input mesh m.
Definition face_requirements.h:451
bool isPerFaceColorAvailable(const MeshType &m)
Returns true if the Color component is available (enabled) in the Face element of the input mesh m.
Definition face_requirements.h:214
constexpr detail::FacesView faces
A view that allows to iterate overt the Face elements of an object.
Definition face.h:52
constexpr detail::VerticesView vertices
A view that allows to iterate over the Vertex elements of an object.
Definition vertex.h:60