Visual Computing Library  devel
Loading...
Searching...
No Matches
load_mesh.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_IO_MESH_GLTF_DETAIL_LOAD_MESH_H
24#define VCL_IO_MESH_GLTF_DETAIL_LOAD_MESH_H
25
26#include <vclib/io/mesh/settings.h>
27
28#include <vclib/algorithms/mesh.h>
29#include <vclib/mesh.h>
30#include <vclib/space/complex.h>
31#include <vclib/space/core.h>
32
33#include <tiny_gltf.h>
34
35#include <regex>
36
37namespace vcl::detail {
38
39enum class GltfAttrType { POSITION, NORMAL, COLOR_0, TEXCOORD_0, INDICES };
40inline const std::array<std::string, 4> GLTF_ATTR_STR {
41 "POSITION",
42 "NORMAL",
43 "COLOR_0",
44 "TEXCOORD_0"};
45
46inline void checkGltfPrimitiveMaterial(
47 const tinygltf::Model& model,
48 const tinygltf::Primitive& p,
49 int& textureImg,
50 bool& hasColor,
51 vcl::Color& color)
52{
53 if (p.material >= 0) { // if the primitive has a material
54 const tinygltf::Material& mat = model.materials[p.material];
55 auto it = mat.values.find("baseColorTexture");
56 if (it != mat.values.end()) { // the material is a texture
57 auto it2 = it->second.json_double_value.find("index");
58 if (it2 != it->second.json_double_value.end()) {
59 textureImg = it2->second; // get the id of the texture
60 }
61 }
62 it = mat.values.find("baseColorFactor");
63 if (it !=
64 mat.values.end()) { // vertex base color, the same for a primitive
65 hasColor = true;
66 const std::vector<double>& vc = it->second.number_array;
67 for (uint i = 0; i < 4; i++)
68 color[i] = vc[i] * 255.0;
69 }
70 }
71}
72
73template<MeshConcept MeshType, typename Scalar>
74bool populateGltfVertices(
75 MeshType& m,
76 const Scalar* posArray,
77 uint stride,
78 uint vertNumber)
79{
80 using PositionType = typename MeshType::VertexType::PositionType;
81
82 uint base = m.addVertices(vertNumber);
83
84 for (uint i = 0; i < vertNumber; ++i) {
85 const Scalar* posBase = reinterpret_cast<const Scalar*>(
86 reinterpret_cast<const char*>(posArray) + (i) *stride);
87 m.vertex(base + i).position() =
88 PositionType(posBase[0], posBase[1], posBase[2]);
89 }
90 return true;
91}
92
93template<MeshConcept MeshType, typename Scalar>
94bool populateGltfVNormals(
95 MeshType& m,
96 uint firstVertex,
97 bool enableOptionalComponents,
98 const Scalar* normArray,
99 unsigned int stride,
100 unsigned int vertNumber)
101{
102 if constexpr (HasPerVertexNormal<MeshType>) {
103 using NormalType = typename MeshType::VertexType::NormalType;
104
105 if (enableOptionalComponents)
106 enableIfPerVertexNormalOptional(m);
107
108 if (isPerVertexNormalAvailable(m)) {
109 for (unsigned int i = 0; i < vertNumber; i++) {
110 const Scalar* normBase = reinterpret_cast<const Scalar*>(
111 reinterpret_cast<const char*>(normArray) + i * stride);
112 m.vertex(firstVertex + i).normal() =
113 NormalType(normBase[0], normBase[1], normBase[2]);
114 }
115 return true;
116 }
117 else {
118 return false;
119 }
120 }
121 else {
122 return false;
123 }
124}
125
126template<MeshConcept MeshType, typename Scalar>
127bool populateGltfVColors(
128 MeshType& m,
129 uint firstVertex,
130 bool enableOptionalComponents,
131 const Scalar* colorArray,
132 unsigned int stride,
133 unsigned int vertNumber,
134 int nElemns)
135{
136 if constexpr (HasPerVertexColor<MeshType>) {
137 if (enableOptionalComponents)
138 enableIfPerVertexColorOptional(m);
139
140 if (isPerVertexColorAvailable(m)) {
141 for (unsigned int i = 0; i < vertNumber * nElemns; i += nElemns) {
142 const Scalar* colorBase = reinterpret_cast<const Scalar*>(
143 reinterpret_cast<const char*>(colorArray) +
144 (i / nElemns) * stride);
145 const auto vi = firstVertex + i / nElemns;
146 vcl::Color c;
147 if constexpr (!std::is_floating_point<Scalar>::value) {
148 uint alpha = nElemns == 4 ? colorBase[3] : 255;
149 c = vcl::Color(
150 colorBase[0], colorBase[1], colorBase[2], alpha);
151 }
152 else {
153 uint alpha = nElemns == 4 ? colorBase[3] * 255 : 255;
154 c = vcl::Color(
155 colorBase[0] * 255,
156 colorBase[1] * 255,
157 colorBase[2] * 255,
158 alpha);
159 }
160 m.vertex(vi).color() = c;
161 }
162 return true;
163 }
164 else {
165 return false;
166 }
167 }
168 else {
169 return false;
170 }
171}
172
173template<MeshConcept MeshType, typename Scalar>
174bool populateGltfVTextCoords(
175 MeshType& m,
176 uint firstVertex,
177 bool enableOptionalComponents,
178 const Scalar* textCoordArray,
179 unsigned int stride,
180 unsigned int vertNumber,
181 int textID)
182{
183 if constexpr (HasPerVertexTexCoord<MeshType>) {
184 using TexCoordType = typename MeshType::VertexType::TexCoordType;
185
186 if (enableOptionalComponents)
187 enableIfPerVertexTexCoordOptional(m);
188
189 if (isPerVertexTexCoordAvailable(m)) {
190 for (unsigned int i = 0; i < vertNumber; i++) {
191 const Scalar* textCoordBase = reinterpret_cast<const Scalar*>(
192 reinterpret_cast<const char*>(textCoordArray) + i * stride);
193
194 m.vertex(firstVertex + i).texCoord() = TexCoordType(
195 textCoordBase[0], 1 - textCoordBase[1], textID);
196 }
197 return true;
198 }
199 else {
200 return false;
201 }
202 }
203 else {
204 return false;
205 }
206}
207
208template<MeshConcept MeshType, typename Scalar>
209bool populateGltfTriangles(
210 MeshType& m,
211 uint firstVertex,
212 const Scalar* triArray,
213 uint triNumber)
214{
215 if constexpr (HasFaces<MeshType>) {
216 if (triArray != nullptr) {
217 uint fi = m.addFaces(triNumber);
218 for (unsigned int i = 0; i < triNumber * 3; i += 3, ++fi) {
219 auto& f = m.face(fi);
220 if constexpr (HasPolygons<MeshType>) {
221 f.resizeVertices(3);
222 }
223 for (int j = 0; j < 3; ++j) {
224 f.setVertex(j, firstVertex + triArray[i + j]);
225 }
226 }
227 }
228 else {
229 triNumber = m.vertexNumber() / 3 - firstVertex;
230 uint fi = m.addFaces(triNumber);
231 for (uint i = 0; i < triNumber * 3; i += 3, ++fi) {
232 auto& f = m.face(fi);
233 for (uint j = 0; j < 3; ++j) {
234 f.setVertex(j, firstVertex + i + j);
235 }
236 }
237 }
238 return true;
239 }
240 else {
241 return false;
242 }
243}
244
260template<typename Scalar, MeshConcept MeshType>
261bool populateGltfAttr(
262 GltfAttrType attr,
263 MeshType& m,
264 uint firstVertex,
265 bool enableOptionalComponents,
266 const Scalar* array,
267 unsigned int stride,
268 unsigned int number,
269 int textID = -1)
270{
271 using enum GltfAttrType;
272
273 switch (attr) {
274 case POSITION: return populateGltfVertices(m, array, stride, number);
275 case NORMAL:
276 return populateGltfVNormals(
277 m, firstVertex, enableOptionalComponents, array, stride, number);
278 case COLOR_0:
279 return populateGltfVColors(
280 m,
281 firstVertex,
282 enableOptionalComponents,
283 array,
284 stride,
285 number,
286 textID);
287 case TEXCOORD_0:
288 return populateGltfVTextCoords(
289 m,
290 firstVertex,
291 enableOptionalComponents,
292 array,
293 stride,
294 number,
295 textID);
296 case INDICES:
297 return populateGltfTriangles(m, firstVertex, array, number / 3);
298 default: return false;
299 }
300}
301
318template<MeshConcept MeshType>
319bool loadGltfAttribute(
320 MeshType& m,
321 uint startingVertex,
322 bool enableOptionalComponents,
323 const tinygltf::Model& model,
324 const tinygltf::Primitive& p,
325 GltfAttrType attr,
326 int textID)
327{
328 using enum GltfAttrType;
329
330 const tinygltf::Accessor* accessor = nullptr;
331
332 // get the accessor associated to the attribute
333 if (attr != INDICES) {
334 auto it = p.attributes.find(GLTF_ATTR_STR[toUnderlying(attr)]);
335
336 if (it != p.attributes.end()) { // accessor found
337 accessor = &model.accessors[it->second];
338 }
339 else if (attr == POSITION) { // if we were looking for POSITION and
340 // didn't find any
341 throw MalformedFileException("File has not 'Position' attribute");
342 }
343 }
344 else { // if the attribute is triangle indices
345 // if the mode is GL_TRIANGLES and we have triangle indices
346 if (p.mode == TINYGLTF_MODE_TRIANGLES && p.indices >= 0 &&
347 (uint) p.indices < model.accessors.size()) {
348 accessor = &model.accessors[p.indices];
349 }
350 }
351
352 // if we found an accessor of the attribute
353 if (accessor) {
354 // bufferview: contains infos on how to access buffer with the accessor
355 const tinygltf::BufferView& posbw =
356 model.bufferViews[accessor->bufferView];
357
358 // data of the whole buffer (vector of bytes);
359 // may contain also other data not associated to our attribute
360 const std::vector<unsigned char>& posdata =
361 model.buffers[posbw.buffer].data;
362
363 // offset where the data of the attribute starts
364 uint posOffset = posbw.byteOffset + accessor->byteOffset;
365 // hack:
366 // if the attribute is a color, textid is used to tell the size of the
367 // color (3 or 4 components)
368 if (attr == COLOR_0) {
369 if (accessor->type == TINYGLTF_TYPE_VEC3)
370 textID = 3;
371 else if (accessor->type == TINYGLTF_TYPE_VEC4)
372 textID = 4;
373 }
374
375 const uint elementSize =
376 tinygltf::GetNumComponentsInType(accessor->type) *
377 tinygltf::GetComponentSizeInBytes(accessor->componentType);
378 const uint stride =
379 (posbw.byteStride > elementSize) ? posbw.byteStride : elementSize;
380
381 // if data is float
382 if (accessor->componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) {
383 // get the starting point of the data as float pointer
384 const float* posArray = (const float*) (posdata.data() + posOffset);
385 return populateGltfAttr(
386 attr,
387 m,
388 startingVertex,
389 enableOptionalComponents,
390 posArray,
391 stride,
392 accessor->count,
393 textID);
394 }
395 // if data is double
396 else if (accessor->componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) {
397 // get the starting point of the data as double pointer
398 const double* posArray =
399 (const double*) (posdata.data() + posOffset);
400 return populateGltfAttr(
401 attr,
402 m,
403 startingVertex,
404 enableOptionalComponents,
405 posArray,
406 stride,
407 accessor->count,
408 textID);
409 }
410 // if data is ubyte
411 else if (
412 accessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) {
413 // get the starting point of the data as uchar pointer
414 const unsigned char* triArray =
415 (const unsigned char*) (posdata.data() + posOffset);
416 return populateGltfAttr(
417 attr,
418 m,
419 startingVertex,
420 enableOptionalComponents,
421 triArray,
422 stride,
423 accessor->count,
424 textID);
425 }
426 // if data is ushort
427 else if (
428 accessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) {
429 // get the starting point of the data as ushort pointer
430 const unsigned short* triArray =
431 (const unsigned short*) (posdata.data() + posOffset);
432 return populateGltfAttr(
433 attr,
434 m,
435 startingVertex,
436 enableOptionalComponents,
437 triArray,
438 stride,
439 accessor->count,
440 textID);
441 }
442 // if data is uint
443 else if (
444 accessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) {
445 // get the starting point of the data as uint pointer
446 const uint* triArray = (const uint*) (posdata.data() + posOffset);
447 return populateGltfAttr(
448 attr,
449 m,
450 startingVertex,
451 enableOptionalComponents,
452 triArray,
453 stride,
454 accessor->count,
455 textID);
456 }
457 }
458 // if accessor not found and attribute is indices, it means that
459 // the mesh is not indexed, and triplets of contiguous vertices
460 // generate triangles
461 else if (attr == INDICES) {
462 // avoid explicitly the point clouds
463 if (p.mode != TINYGLTF_MODE_POINTS) {
464 // this case is managed when passing nullptr as data
465 return populateGltfAttr<unsigned char>(
466 attr,
467 m,
468 startingVertex,
469 enableOptionalComponents,
470 nullptr,
471 0,
472 0);
473 }
474 }
475 return false;
476}
477
484template<
485 MeshConcept MeshType,
486 Matrix44Concept MatrixType,
487 LoggerConcept LogType>
488void loadGltfMeshPrimitive(
489 MeshType& m,
490 MeshInfo& info,
491 const tinygltf::Model& model,
492 const tinygltf::Primitive& p,
493 const MatrixType& transf,
494 const LoadSettings& settings,
495 LogType& log)
496{
497 int textureImg = -1; // id of the texture associated to the material
498 bool vTex = false; // used if a material has a texture
499 bool vCol =
500 false; // used if a material has a base color for all the primitive
501 vcl::Color col; // the base color, to be set to all the vertices
502 checkGltfPrimitiveMaterial(model, p, textureImg, vCol, col);
503
504 if constexpr (HasTexturePaths<MeshType>) {
505 if (textureImg != -1) { // if we found a texture
506 vTex = true;
507 const tinygltf::Image& img =
508 model.images[model.textures[textureImg].source];
509 // add the path of the texture to the mesh
510 std::string uri = img.uri;
511 uri = std::regex_replace(uri, std::regex("\\%20"), " ");
512
513 bool textureAdded = false;
514 if constexpr (HasTextureImages<MeshType>) {
515 if (img.image.size() > 0) {
516 if (img.bits == 8 || img.component == 4) {
517 if (uri.empty()) {
518 uri = "texture_" + std::to_string(textureImg);
519 }
520 vcl::Texture txt(
521 Image(img.image.data(), img.width, img.height),
522 uri);
523 m.pushTexture(txt);
524 textureAdded = true;
525 }
526 }
527 }
528 if (!textureAdded) {
529 // if the image is not valid, just add the path
530 m.pushTexturePath(uri);
531 }
532 textureImg = m.textureNumber() - 1; // update the texture id
533 }
534 }
535
536 uint firstVertex = m.vertexNumber();
537
538 // load vertex position attribute
539 loadGltfAttribute(
540 m,
541 firstVertex,
542 settings.enableOptionalComponents,
543 model,
544 p,
545 GltfAttrType::POSITION,
546 textureImg);
547 info.setVertices();
548
549 bool lvn = loadGltfAttribute(
550 m,
551 firstVertex,
552 settings.enableOptionalComponents,
553 model,
554 p,
555 GltfAttrType::NORMAL,
556 textureImg);
557 info.setPerVertexNormal(lvn);
558
559 if (vCol) {
560 if constexpr (HasPerVertexColor<MeshType>) {
561 if (settings.enableOptionalComponents) {
562 enableIfPerVertexColorOptional(m);
563 }
564 if (isPerVertexColorAvailable(m)) {
565 for (auto& v : m.vertices())
566 v.color() = col;
567 info.setPerVertexColor();
568 }
569 }
570 }
571
572 bool lvc = loadGltfAttribute(
573 m,
574 firstVertex,
575 settings.enableOptionalComponents,
576 model,
577 p,
578 GltfAttrType::COLOR_0,
579 textureImg);
580 if (lvc) {
581 info.setPerVertexColor();
582 }
583
584 bool lvt = loadGltfAttribute(
585 m,
586 firstVertex,
587 settings.enableOptionalComponents,
588 model,
589 p,
590 GltfAttrType::TEXCOORD_0,
591 textureImg);
592 if (lvt) {
593 info.setPerVertexTexCoord();
594 }
595
596 bool lti = loadGltfAttribute(
597 m,
598 firstVertex,
599 settings.enableOptionalComponents,
600 model,
601 p,
602 GltfAttrType::INDICES,
603 textureImg);
604 if (lti) {
605 info.setTriangleMesh();
606 info.setFaces();
607 info.setPerFaceVertexReferences();
608 }
609
610 if (HasTransformMatrix<MeshType>) {
611 m.transformMatrix() = transf;
612 }
613 else {
614 // if the mesh does not have a transform matrix, apply the
615 // transformation matrix to the vertices
616 vcl::applyTransformMatrix(m, transf);
617 }
618}
619
628template<
629 MeshConcept MeshType,
630 Matrix44Concept MatrixType,
631 LoggerConcept LogType>
632void gltfLoadMesh(
633 MeshType& m,
634 MeshInfo& info,
635 const tinygltf::Mesh& tm,
636 const tinygltf::Model& model,
637 const MatrixType& transf,
638 const LoadSettings& settings,
639 LogType& log)
640{
641 if constexpr (HasName<MeshType>) {
642 if (!tm.name.empty()) {
643 m.name() = tm.name;
644 }
645 }
646
647 // TODO: fix logger - save the progress state each time a new task is
648 // started
649 // log.startProgress("Reading primitives", tm.primitives.size());
650
651 // for each primitive, load it into the mesh
652 for (uint i = 0; const tinygltf::Primitive& p : tm.primitives) {
653 loadGltfMeshPrimitive(m, info, model, p, transf, settings, log);
654 // log.progress(++i);
655 }
656
657 // log.endProgress();
658
659 log.log(
660 "Loaded mesh '" + tm.name + "' with " +
661 std::to_string(tm.primitives.size()) + " primitives.",
662 LogType::LogLevel::MESSAGE_LOG);
663}
664
665} // namespace vcl::detail
666
667#endif // VCL_IO_MESH_GLTF_DETAIL_LOAD_MESH_H
The Color class represents a 32 bit color.
Definition color.h:48
Definition texture.h:33
constexpr detail::VerticesView vertices
A view that allows to iterate over the Vertex elements of an object.
Definition vertex.h:92