Visual Computing Library  devel
Loading...
Searching...
No Matches
load.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_OBJ_LOAD_H
24#define VCL_IO_MESH_OBJ_LOAD_H
25
26#include "material.h"
27
28#include <vclib/io/file_info.h>
29#include <vclib/io/image/load.h>
30#include <vclib/io/mesh/settings.h>
31#include <vclib/io/read.h>
32
33#include <vclib/algorithms/mesh.h>
34#include <vclib/space/complex.h>
35#include <vclib/space/core.h>
36
37#include <algorithm>
38#include <map>
39
40namespace vcl {
41
42namespace detail {
43
44template<MeshConcept MeshType>
45using ObjNormalsMap = std::conditional_t<
46 HasPerVertexNormal<MeshType>,
47 std::map<uint, typename MeshType::VertexType::NormalType>,
48 std::map<uint, Point3d>>;
49
50template<MeshConcept MeshType>
51void loadObjMaterials(
52 std::map<std::string, ObjMaterial>& materialMap,
53 MeshType& mesh,
54 std::istream& stream,
55 MeshInfo& loadedInfo,
56 const LoadSettings& settings)
57{
58 std::string matName;
59 ObjMaterial mat;
60
61 do {
62 Tokenizer tokens = readAndTokenizeNextNonEmptyLineNoThrow(stream);
63 if (stream) {
64 // counter for texture images, used when mesh has no texture files
65 uint nt = 0;
66 Tokenizer::iterator token = tokens.begin();
67 std::string header = *token++;
68 if (header == "newmtl") {
69 if (!matName.empty())
70 materialMap[matName] = mat;
71 mat = ObjMaterial();
72 matName = *token;
73 }
74 if (header == "Ka") {
75 if (tokens.size() >= 4) {
76 if (*token != "spectral" && *token != "xyz") {
77 mat.Ka.x() = io::readFloat<float>(token);
78 mat.Ka.y() = io::readFloat<float>(token);
79 mat.Ka.z() = io::readFloat<float>(token);
80 }
81 }
82 }
83 if (header == "Kd") {
84 if (tokens.size() >= 4) {
85 if (*token != "spectral" && *token != "xyz") {
86 mat.Kd.x() = io::readFloat<float>(token);
87 mat.Kd.y() = io::readFloat<float>(token);
88 mat.Kd.z() = io::readFloat<float>(token);
89 mat.hasColor = true;
90 }
91 }
92 }
93 if (header == "Ks") {
94 if (tokens.size() >= 4) {
95 if (*token != "spectral" && *token != "xyz") {
96 mat.Ks.x() = io::readFloat<float>(token);
97 mat.Ks.y() = io::readFloat<float>(token);
98 mat.Ks.z() = io::readFloat<float>(token);
99 }
100 }
101 }
102 if (header == "d") {
103 if ((*token)[0] == '-')
104 token++;
105 mat.d = io::readFloat<float>(token);
106 }
107 if (header == "Tr") {
108 if ((*token)[0] == '-')
109 token++;
110 mat.d = 1 - io::readFloat<float>(token);
111 }
112 if (header == "Ns") {
113 mat.Ns = io::readFloat<float>(token);
114 }
115 if (header == "illum") {
116 mat.illum = io::readFloat<int>(token);
117 }
118 if (header == "map_Kd") {
119 // need to manage args
120 while ((*token)[0] == '-') {
121 if (*token == "-o" || *token == "-s" || *token == "-t") {
122 // ignore the argument and the three values
123 ++token;
124 ++token;
125 ++token;
126 ++token;
127 }
128 if (*token == "-mm") {
129 // ignore the argument and the two values
130 ++token;
131 ++token;
132 ++token;
133 }
134 if (*token == "-blendu" || *token == "-blendv" ||
135 *token == "-cc" || *token == "-clamp" ||
136 *token == "-texres") {
137 // ignore the argument and the value
138 ++token;
139 ++token;
140 }
141 }
142 mat.map_Kd = *token;
143 // replace backslashes with slashes - windows compatibility
144 std::ranges::replace(mat.map_Kd, '\\', '/');
145 mat.hasTexture = true;
146 if constexpr (HasTexturePaths<MeshType>) {
147 loadedInfo.setTextures();
148 mat.mapId = mesh.textureNumber();
149 mesh.pushTexturePath(mat.map_Kd);
150 }
151 else {
152 mat.mapId = nt++;
153 }
154 }
155 }
156 } while (stream);
157 if (!matName.empty())
158 materialMap[matName] = mat;
159}
160
161template<MeshConcept MeshType>
162void loadObjMaterials(
163 std::map<std::string, ObjMaterial>& materialMap,
164 MeshType& mesh,
165 const std::string& mtllib,
166 MeshInfo& loadedInfo,
167 const LoadSettings& settings)
168{
169 std::ifstream file = openInputFileStream(mtllib);
170 loadObjMaterials(materialMap, mesh, file, loadedInfo, settings);
171}
172
173template<MeshConcept MeshType>
174void readObjVertex(
175 MeshType& m,
176 Tokenizer::iterator& token,
177 MeshInfo& loadedInfo,
178 const Tokenizer& tokens,
179 const ObjMaterial& currentMaterial,
180 const LoadSettings& settings)
181{
182 uint vid = m.addVertex();
183 for (uint i = 0; i < 3; ++i) {
184 m.vertex(vid).position()[i] = io::readDouble<double>(token);
185 }
186 if constexpr (HasPerVertexColor<MeshType>) {
187 if (vid == 0) {
188 // if the current material has a valid color, or the file stores the
189 // vertex color in the non-standard way (color values after the
190 // positions)
191 if (currentMaterial.hasColor || tokens.size() > 6) {
192 if (settings.enableOptionalComponents) {
193 enableIfPerVertexColorOptional(m);
194 loadedInfo.setPerVertexColor();
195 }
196 else {
197 if (isPerVertexColorAvailable(m))
198 loadedInfo.setPerVertexColor();
199 }
200 }
201 }
202 if (loadedInfo.hasPerVertexColor()) {
203 // the file has the nonstandard way to store vertex colors, after
204 // the positions...
205 if (tokens.size() > 6) {
206 m.vertex(vid).color().setRedF(io::readFloat<float>(token));
207 m.vertex(vid).color().setGreenF(io::readFloat<float>(token));
208 m.vertex(vid).color().setBlueF(io::readFloat<float>(token));
209 }
210 else if (currentMaterial.hasColor) {
211 m.vertex(vid).color() = currentMaterial.color();
212 }
213 }
214 }
215}
216
217template<MeshConcept MeshType>
218void readObjVertexNormal(
219 MeshType& m,
220 detail::ObjNormalsMap<MeshType>& mapNormalsCache,
221 uint vn,
222 Tokenizer::iterator& token,
223 MeshInfo& loadedInfo,
224 const LoadSettings& settings)
225{
226 using NormalType = MeshType::VertexType::NormalType;
227
228 // first, need to check if I can store normals in the mesh
229 if (vn == 0) {
230 if (settings.enableOptionalComponents) {
231 enableIfPerVertexNormalOptional(m);
232 loadedInfo.setPerVertexNormal();
233 }
234 else {
235 if (isPerVertexNormalAvailable(m))
236 loadedInfo.setPerVertexNormal();
237 }
238 }
239 if (loadedInfo.hasPerVertexNormal()) {
240 // read the normal
241 NormalType n;
242 for (uint i = 0; i < 3; ++i) {
243 n[i] = io::readDouble<typename NormalType::ScalarType>(token);
244 }
245 // I can store the normal in its vertex
246 if (m.vertexNumber() > vn) {
247 m.vertex(vn).normal() = n;
248 }
249 // read the normal and save it in the cache map, because we still don't
250 // have read the vertex corresponding to the current normal
251 else {
252 mapNormalsCache[vn] = n;
253 }
254 }
255}
256
257template<FaceMeshConcept MeshType>
258void readObjFace(
259 MeshType& m,
260 MeshInfo& loadedInfo,
261 const Tokenizer& tokens,
262 const std::vector<TexCoordIndexedd>& wedgeTexCoords,
263 const ObjMaterial& currentMaterial,
264 const LoadSettings& settings)
265{
266 using FaceType = MeshType::FaceType;
267
268 std::vector<uint> vids;
269 std::vector<uint> wids;
270
271 loadedInfo.updateMeshType(tokens.size() - 1);
272
273 // actual read - load vertex indices and texcoords indices, if present
274 Tokenizer::iterator token = tokens.begin();
275 ++token;
276 vids.resize(tokens.size() - 1);
277 wids.reserve(tokens.size() - 1);
278 for (uint i = 0; i < tokens.size() - 1; ++i) {
279 Tokenizer subt(*token, '/', false);
280 auto t = subt.begin();
281 vids[i] = io::readUInt<uint>(t) - 1;
282 if (subt.size() > 1) {
283 if (!t->empty()) {
284 wids.push_back(io::readUInt<uint>(t) - 1);
285 }
286 }
287 ++token;
288 }
289
290 // add the face
291 uint fid = m.addFace();
292 FaceType& f = m.face(fid);
293
294 // check if we need to split the face we read into triangles
295 bool splitFace = false;
296 // we have a polygonal mesh, no need to split
297 if constexpr (FaceType::VERTEX_NUMBER < 0) {
298 // need to resize to the right number of verts
299 f.resizeVertices(tokens.size() - 1);
300 }
301 else if (FaceType::VERTEX_NUMBER != tokens.size() - 1) {
302 // we have faces with static sizes (triangles), but we are loading faces
303 // with number of verts > 3. Need to split the face we are loading in n
304 // faces!
305 splitFace = true;
306 }
307
308 // create the face in the mesh, for now we manage only vertex indices
309 if (!splitFace) { // no need to split face case
310 for (uint i = 0; i < vids.size(); ++i) {
311 if (vids[i] >= m.vertexNumber()) {
312 throw MalformedFileException(
313 "Bad vertex index for face " + std::to_string(fid));
314 }
315 f.setVertex(i, vids[i]);
316 }
317 }
318 else { // split needed
319 addTriangleFacesFromPolygon(m, f, vids);
320 }
321
322 // color
323 if (HasPerFaceColor<MeshType>) {
324 // if the first face, we need to check if I can store colors
325 if (fid == 0) {
326 // if the current material has no color, we assume that the file has
327 // no face color
328 if (currentMaterial.hasColor) {
329 if (settings.enableOptionalComponents) {
331 loadedInfo.setPerFaceColor();
332 }
333 else {
335 loadedInfo.setPerFaceColor();
336 }
337 }
338 }
339 if (loadedInfo.hasPerFaceColor()) {
340 if (currentMaterial.hasColor) {
341 // in case the loaded polygon has been triangulated in the last
342 // n triangles of mesh
343 for (uint ff = fid; ff < m.faceNumber(); ++ff) {
344 m.face(ff).color() = currentMaterial.color();
345 }
346 }
347 }
348 }
349
350 // wedge texcoords
351 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
352 // first, need to check if I can store wedge texcoords in the mesh
353 if (fid == 0) {
354 // if the current face has the right number of wedge texcoords, we
355 // assume that we can load wedge texcoords
356 if (wids.size() == vids.size()) {
357 if (settings.enableOptionalComponents) {
359 loadedInfo.setPerFaceWedgeTexCoords();
360 }
361 else {
363 loadedInfo.setPerFaceWedgeTexCoords();
364 }
365 }
366 }
367 if (loadedInfo.hasPerFaceWedgeTexCoords()) {
368 if (wids.size() == vids.size()) {
369 if (!splitFace) { // there wasn't a triangulation of the face
370 // it is safe to assign each wedge texcoord to its position
371 // in the face
372 for (uint i = 0; i < wids.size(); ++i) {
373 if (wids[i] >= wedgeTexCoords.size()) {
374 throw MalformedFileException(
375 "Bad texcoord index for face " +
376 std::to_string(fid));
377 }
378 f.wedgeTexCoord(i) =
379 ((vcl::TexCoordd) wedgeTexCoords[wids[i]])
380 .cast<typename FaceType::WedgeTexCoordType::
381 ScalarType>();
382 if (currentMaterial.hasTexture) {
383 f.textureIndex() = currentMaterial.mapId;
384 }
385 }
386 }
387 else {
388 // take read texcoords and map them in the triangulated
389 // faces for each face of the triangulation of the polygon
390 for (uint ff = fid; ff < m.faceNumber(); ++ff) {
391 FaceType& f = m.face(ff);
392 // for each vertex of the face
393 for (uint i = 0; i < f.vertexNumber(); ++i) {
394 uint vid = m.index(f.vertex(i));
395 // find the position of the vertex in the vids array
396 auto it = std::find(vids.begin(), vids.end(), vid);
397 assert(it != vids.end());
398 uint pos = it - vids.begin();
399 // check that the texcoord id is valid
400 if (wids[pos] >= wedgeTexCoords.size()) {
401 throw MalformedFileException(
402 "Bad texcoord index for face " +
403 std::to_string(fid));
404 }
405 // set the wedge texcoord in the same position of
406 // the vertex
407 f.wedgeTexCoord(i) =
408 ((vcl::TexCoordd) wedgeTexCoords[wids[pos]])
409 .cast<typename FaceType::WedgeTexCoordType::
410 ScalarType>();
411 if (currentMaterial.hasTexture) {
412 f.textureIndex() = currentMaterial.mapId;
413 }
414 }
415 }
416 }
417 }
418 }
419 }
420}
421
422template<EdgeMeshConcept MeshType>
423void readObjEdge(
424 MeshType& m,
425 MeshInfo& loadedInfo,
426 const Tokenizer& tokens,
427 const ObjMaterial& currentMaterial,
428 const LoadSettings& settings)
429{
430 using EdgeType = MeshType::EdgeType;
431
432 // add the edge
433 uint eid = m.addEdge();
434 EdgeType& e = m.edge(eid);
435
436 // actual read - load vertex indices
437 Tokenizer::iterator token = tokens.begin();
438 ++token;
439 uint vid1 = io::readUInt<uint>(token) - 1;
440 uint vid2 = io::readUInt<uint>(token) - 1;
441 e.setVertices(vid1, vid2);
442
443 // color
444 if (HasPerEdgeColor<MeshType>) {
445 // if the first edge, we need to check if I can store colors
446 if (eid == 0) {
447 // if the current material has no color, we assume that the file has
448 // no edge color
449 if (currentMaterial.hasColor) {
450 if (settings.enableOptionalComponents) {
452 loadedInfo.setPerEdgeColor();
453 }
454 else {
456 loadedInfo.setPerEdgeColor();
457 }
458 }
459 }
460 if (loadedInfo.hasPerEdgeColor()) {
461 if (currentMaterial.hasColor) {
462 // set the current color to the edge
463 m.edge(eid).color() = currentMaterial.color();
464 }
465 }
466 }
467}
468
488template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
489void loadObj(
490 MeshType& m,
491 std::istream& inputObjStream,
492 const std::vector<std::istream*>& inputMtlStreams,
493 MeshInfo& loadedInfo,
494 const std::string& filename = "",
495 bool ignoreMtlLib = false,
496 const LoadSettings& settings = LoadSettings(),
497 LogType& log = nullLogger)
498{
499 loadedInfo.clear();
500
501 // save normals if they can't be stored directly into vertices
502 detail::ObjNormalsMap<MeshType> mapNormalsCache;
503 uint vn = 0; // number of vertex normals read
504 // save array of texcoords, that are stored later (into wedges when loading
505 // faces or into vertices as a fallback)
506 std::vector<TexCoordIndexedd> texCoords;
507
508 // map of materials loaded
509 std::map<std::string, detail::ObjMaterial> materialMap;
510
511 // load materials from the material files, if any
512 for (auto* stream : inputMtlStreams) {
513 detail::loadObjMaterials(materialMap, m, *stream, loadedInfo, settings);
514 }
515
516 // the current material, set by 'usemtl'
517 detail::ObjMaterial currentMaterial;
518
519 if constexpr (HasTexturePaths<MeshType>) {
520 m.meshBasePath() = FileInfo::pathWithoutFileName(filename);
521 }
522
523 if constexpr (HasName<MeshType>) {
524 m.name() = FileInfo::fileNameWithoutExtension(filename);
525 }
526
527 inputObjStream.seekg(0, inputObjStream.end);
528 std::size_t fsize = inputObjStream.tellg();
529 inputObjStream.seekg(0, inputObjStream.beg);
530 log.startProgress("Loading OBJ file", fsize);
531
532 // cycle that reads line by line
533 do {
534 Tokenizer tokens =
535 readAndTokenizeNextNonEmptyLineNoThrow(inputObjStream);
536 if (inputObjStream) {
537 Tokenizer::iterator token = tokens.begin();
538 std::string header = *token++;
539 if (header == "mtllib" && !ignoreMtlLib) { // material file
540 // we load the material file if they are not ignored
541 std::string mtlfile =
542 FileInfo::pathWithoutFileName(filename) + *token;
543 try {
544 detail::loadObjMaterials(
545 materialMap, m, mtlfile, loadedInfo, settings);
546 }
547 catch (CannotOpenFileException) {
548 log.log(
549 "Cannot open material file " + mtlfile,
550 LogType::WARNING_LOG);
551 }
552 }
553 // use a new material - change currentMaterial
554 if (header == "usemtl") {
555 std::string matname = *token;
556 auto it = materialMap.find(matname);
557 if (it != materialMap.end()) {
558 currentMaterial = it->second;
559 }
560 else { // material not found - warning
561 log.log(
562 "Material " + matname + " not found.",
563 LogType::WARNING_LOG);
564 }
565 }
566 // read vertex (and for some non-standard obj files, also vertex
567 // color)
568 if (header == "v") {
569 loadedInfo.setVertices();
570 loadedInfo.setPerVertexPosition();
571 detail::readObjVertex(
572 m, token, loadedInfo, tokens, currentMaterial, settings);
573 }
574 // read vertex normal (and save in vn how many normals we read)
575 if (header == "vn") {
576 loadedInfo.setPerVertexNormal();
577 if constexpr (HasPerVertexNormal<MeshType>) {
578 detail::readObjVertexNormal(
579 m, mapNormalsCache, vn, token, loadedInfo, settings);
580 vn++;
581 }
582 }
583 // read texcoords and save them in the vector of texcoords, we will
584 // store them in the mesh later
585 if constexpr (
586 HasPerVertexTexCoord<MeshType> ||
587 HasPerFaceWedgeTexCoords<MeshType>) {
588 if (header == "vt") {
589 // save the texcoord for later
590 TexCoordIndexedd tf;
591 for (uint i = 0; i < 2; ++i) {
592 tf[i] = io::readDouble<double>(token);
593 }
594 if (currentMaterial.hasTexture) {
595 tf.index() = currentMaterial.mapId;
596 }
597 texCoords.push_back(tf);
598 }
599 }
600 // read faces and manage:
601 // - color
602 // - eventual texcoords
603 // - possibility to split polygonal face into several triangles
604 if (header == "f") {
605 loadedInfo.setFaces();
606 loadedInfo.setPerFaceVertexReferences();
607 if constexpr (HasFaces<MeshType>) {
608 detail::readObjFace(
609 m,
610 loadedInfo,
611 tokens,
612 texCoords,
613 currentMaterial,
614 settings);
615 }
616 }
617 // read edges and manage their color
618 if (header == "l") {
619 loadedInfo.setEdges();
620 loadedInfo.setPerEdgeVertexReferences();
621 if constexpr (HasEdges<MeshType>) {
622 detail::readObjEdge(
623 m, loadedInfo, tokens, currentMaterial, settings);
624 }
625 }
626 log.progress(inputObjStream.tellg());
627 }
628 } while (inputObjStream);
629
630 if constexpr (HasPerVertexNormal<MeshType>) {
631 // set all vertex normals that have not been stored in vertices
632 for (const auto& p : mapNormalsCache) {
633 if (p.first < m.vertexNumber()) {
634 m.vertex(p.first).normal() = p.second;
635 }
636 }
637 }
638 if constexpr (HasPerVertexTexCoord<MeshType>) {
639 using VertexType = MeshType::VertexType;
640 if (!loadedInfo.hasPerFaceWedgeTexCoords()) {
641 // we can set the loaded texCoords to vertices, also if they are not
642 // supported in obj
643 if (texCoords.size() == m.vertexNumber()) {
644 if (settings.enableOptionalComponents) {
645 enableIfPerVertexTexCoordOptional(m);
646 loadedInfo.setPerVertexTexCoord();
647 }
648 else {
649 if (isPerVertexTexCoordAvailable(m))
650 loadedInfo.setPerVertexTexCoord();
651 }
652 if (loadedInfo.hasPerVertexTexCoord()) {
653 uint i = 0;
654 for (VertexType& v : m.vertices()) {
655 v.texCoord() =
656 texCoords[i++]
657 .cast<typename VertexType::TexCoordType::
658 ScalarType>();
659 }
660 }
661 }
662 }
663 }
664
665 if constexpr (HasTextureImages<MeshType>) {
666 if (settings.loadTextureImages) {
667 for (Texture& texture : m.textures()) {
668 texture.image() = loadImage(m.meshBasePath() + texture.path());
669 if (texture.image().isNull()) {
670 log.log(
671 "Cannot load texture " + texture.path(),
672 LogType::WARNING_LOG);
673 }
674 }
675 }
676 }
677
678 log.endProgress();
679}
680
681} // namespace detail
682
711template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
713 MeshType& m,
714 std::istream& inputObjStream,
715 const std::vector<std::istream*>& inputMtlStreams,
717 const LoadSettings& settings = LoadSettings(),
718 LogType& log = nullLogger)
719{
720 detail::loadObj(
721 m,
725 "",
726 true,
727 settings,
728 log);
729}
730
758template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
760 MeshType& m,
761 const std::string& filename,
763 const LoadSettings& settings = LoadSettings(),
764 LogType& log = nullLogger)
765{
766 std::ifstream file = openInputFileStream(filename);
767
768 // some obj files do not declare the material file name with mtllib, but
769 // they assume that material file has the same name of the obj file.
770 // Therefore, we first load this file if it exists.
773 ".mtl";
774
775 std::ifstream f;
776 std::vector<std::istream*> mtlStreams;
777 try {
778 f = openInputFileStream(stdmtlfile);
779 mtlStreams.push_back(&f);
780 }
782 // nothing to do, this file was missing, but this was a fallback for
783 // some type of files...
784 }
785
786 detail::loadObj(
787 m, file, mtlStreams, loadedInfo, filename, false, settings, log);
788}
789
790} // namespace vcl
791
792#endif // VCL_IO_MESH_OBJ_LOAD_H
A class representing a box in N-dimensional space.
Definition box.h:46
PointT size() const
Computes the size of the box.
Definition box.h:267
Exception thrown when the file cannot be opened.
Definition exceptions.h:58
static std::string fileNameWithoutExtension(const std::string &fullpath)
Get the file name without extension of a file.
Definition file_info.h:220
static std::string pathWithoutFileName(const std::string &fullpath)
Get the path of a file.
Definition file_info.h:200
A simple class that allows to store which elements and their components have been imported/loaded or ...
Definition mesh_info.h:76
NullLogger nullLogger
The nullLogger object is an object of type NullLogger that is used as default argument in the functio...
Definition null_logger.h:123
bool isPerEdgeColorAvailable(const MeshType &m)
Returns true if the Color component is available (enabled) in the Edge element of the input mesh m.
Definition edge_requirements.h:370
bool enableIfPerEdgeColorOptional(MeshType &m)
If the input mesh has a EdgeContainer, and the Edge Element has a Color Component,...
Definition edge_requirements.h:394
bool enableIfPerFaceWedgeTexCoordsOptional(MeshType &m)
If the input mesh has a FaceContainer, and the Face Element has a WedgeTexCoords Component,...
Definition face_requirements.h:859
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:833
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:476
bool enableIfPerFaceColorOptional(MeshType &m)
If the input mesh has a FaceContainer, and the Face Element has a Color Component,...
Definition face_requirements.h:500
void loadObj(MeshType &m, std::istream &inputObjStream, const std::vector< std::istream * > &inputMtlStreams, MeshInfo &loadedInfo, const LoadSettings &settings=LoadSettings(), LogType &log=nullLogger)
Loads from the given input obj stream and puts the content into the mesh m.
Definition load.h:712
constexpr detail::VerticesView vertices
A view that allows to iterate over the Vertex elements of an object.
Definition vertex.h:92
The LoadSettings structure contains the settings that can be used to load a mesh from a stream/file.
Definition settings.h:35