23#ifndef VCL_LOAD_SAVE_OBJ_LOAD_H
24#define VCL_LOAD_SAVE_OBJ_LOAD_H
28#include <vclib/algorithms/mesh/face_topology.h>
29#include <vclib/io/file_info.h>
30#include <vclib/io/read.h>
31#include <vclib/load_save/settings.h>
32#include <vclib/misc/logger.h>
33#include <vclib/space/complex/mesh_info.h>
34#include <vclib/space/core/texture.h>
43template<MeshConcept MeshType>
44using ObjNormalsMap = std::conditional_t<
45 HasPerVertexNormal<MeshType>,
46 std::map<uint, typename MeshType::VertexType::NormalType>,
47 std::map<uint, Point3d>>;
49template<MeshConcept MeshType>
51 std::map<std::string, ObjMaterial>& materialMap,
55 const LoadSettings& settings)
61 Tokenizer tokens = readAndTokenizeNextNonEmptyLineNoThrow(stream);
65 Tokenizer::iterator token = tokens.begin();
66 std::string header = *token++;
67 if (header ==
"newmtl") {
69 materialMap[matName] = mat;
74 if (tokens.size() >= 4) {
75 if (*token !=
"spectral" && *token !=
"xyz") {
76 mat.Ka.x() = io::readFloat<float>(token);
77 mat.Ka.y() = io::readFloat<float>(token);
78 mat.Ka.z() = io::readFloat<float>(token);
83 if (tokens.size() >= 4) {
84 if (*token !=
"spectral" && *token !=
"xyz") {
85 mat.Kd.x() = io::readFloat<float>(token);
86 mat.Kd.y() = io::readFloat<float>(token);
87 mat.Kd.z() = io::readFloat<float>(token);
93 if (tokens.size() >= 4) {
94 if (*token !=
"spectral" && *token !=
"xyz") {
95 mat.Ks.x() = io::readFloat<float>(token);
96 mat.Ks.y() = io::readFloat<float>(token);
97 mat.Ks.z() = io::readFloat<float>(token);
102 if ((*token)[0] ==
'-')
104 mat.d = io::readFloat<float>(token);
106 if (header ==
"Tr") {
107 if ((*token)[0] ==
'-')
109 mat.d = 1 - io::readFloat<float>(token);
111 if (header ==
"Ns") {
112 mat.Ns = io::readFloat<float>(token);
114 if (header ==
"illum") {
115 mat.illum = io::readFloat<int>(token);
117 if (header ==
"map_Kd") {
119 while ((*token)[0] ==
'-') {
120 if (*token ==
"-o" || *token ==
"-s" || *token ==
"-t") {
127 if (*token ==
"-mm") {
133 if (*token ==
"-blendu" || *token ==
"-blendv" ||
134 *token ==
"-cc" || *token ==
"-clamp" ||
135 *token ==
"-texres") {
143 std::ranges::replace(mat.map_Kd,
'\\',
'/');
144 mat.hasTexture =
true;
145 if constexpr (HasTexturePaths<MeshType>) {
146 loadedInfo.setTextures();
147 mat.mapId = mesh.textureNumber();
148 mesh.pushTexturePath(mat.map_Kd);
156 if (!matName.empty())
157 materialMap[matName] = mat;
160template<MeshConcept MeshType>
161void loadObjMaterials(
162 std::map<std::string, ObjMaterial>& materialMap,
164 const std::string& mtllib,
165 MeshInfo& loadedInfo,
166 const LoadSettings& settings)
168 std::ifstream file = openInputFileStream(mtllib);
169 loadObjMaterials(materialMap, mesh, file, loadedInfo, settings);
172template<MeshConcept MeshType>
175 Tokenizer::iterator& token,
176 MeshInfo& loadedInfo,
177 const Tokenizer& tokens,
178 const ObjMaterial& currentMaterial,
179 const LoadSettings& settings)
182 if (m.vertexNumber() == 0) {
183 loadedInfo.setVertices();
184 loadedInfo.setVertexCoords();
186 uint vid = m.addVertex();
187 for (uint i = 0; i < 3; ++i) {
188 m.vertex(vid).coord()[i] = io::readDouble<double>(token);
190 if constexpr (HasPerVertexColor<MeshType>) {
195 if (currentMaterial.hasColor || tokens.size() > 6) {
196 if (settings.enableOptionalComponents) {
197 enableIfPerVertexColorOptional(m);
198 loadedInfo.setVertexColors();
201 if (isPerVertexColorAvailable(m))
202 loadedInfo.setVertexColors();
206 if (loadedInfo.hasVertexColors()) {
209 if (tokens.size() > 6) {
210 m.vertex(vid).color().setRedF(io::readFloat<float>(token));
211 m.vertex(vid).color().setGreenF(io::readFloat<float>(token));
212 m.vertex(vid).color().setBlueF(io::readFloat<float>(token));
214 else if (currentMaterial.hasColor) {
215 m.vertex(vid).color() = currentMaterial.color();
221template<MeshConcept MeshType>
222void readObjVertexNormal(
224 detail::ObjNormalsMap<MeshType>& mapNormalsCache,
226 Tokenizer::iterator& token,
227 MeshInfo& loadedInfo,
228 const LoadSettings& settings)
230 using NormalType = MeshType::VertexType::NormalType;
234 if (settings.enableOptionalComponents) {
235 enableIfPerVertexNormalOptional(m);
236 loadedInfo.setVertexNormals();
239 if (isPerVertexNormalAvailable(m))
240 loadedInfo.setVertexNormals();
243 if (loadedInfo.hasVertexNormals()) {
246 for (uint i = 0; i < 3; ++i) {
247 n[i] = io::readDouble<typename NormalType::ScalarType>(token);
250 if (m.vertexNumber() > vn) {
251 m.vertex(vn).normal() = n;
256 mapNormalsCache[vn] = n;
261template<FaceMeshConcept MeshType>
264 MeshInfo& loadedInfo,
265 const Tokenizer& tokens,
266 const std::vector<TexCoordIndexedd>& wedgeTexCoords,
267 const ObjMaterial& currentMaterial,
268 const LoadSettings& settings)
270 using FaceType = MeshType::FaceType;
272 std::vector<uint> vids;
273 std::vector<uint> wids;
275 loadedInfo.updateMeshType(tokens.size() - 1);
278 Tokenizer::iterator token = tokens.begin();
280 vids.resize(tokens.size() - 1);
281 wids.reserve(tokens.size() - 1);
282 for (uint i = 0; i < tokens.size() - 1; ++i) {
283 Tokenizer subt(*token,
'/',
false);
284 auto t = subt.begin();
285 vids[i] = io::readUInt<uint>(t) - 1;
286 if (subt.size() > 1) {
288 wids.push_back(io::readUInt<uint>(t) - 1);
295 uint fid = m.addFace();
296 FaceType& f = m.face(fid);
299 bool splitFace =
false;
301 if constexpr (FaceType::VERTEX_NUMBER < 0) {
303 f.resizeVertices(tokens.size() - 1);
305 else if (FaceType::VERTEX_NUMBER != tokens.size() - 1) {
314 for (uint i = 0; i < vids.size(); ++i) {
315 if (vids[i] >= m.vertexNumber()) {
316 throw MalformedFileException(
317 "Bad vertex index for face " + std::to_string(fid));
319 f.setVertex(i, vids[i]);
323 addTriangleFacesFromPolygon(m, f, vids);
327 if (HasPerFaceColor<MeshType>) {
332 if (currentMaterial.hasColor) {
333 if (settings.enableOptionalComponents) {
335 loadedInfo.setFaceColors();
339 loadedInfo.setFaceColors();
343 if (loadedInfo.hasFaceColors()) {
344 if (currentMaterial.hasColor) {
347 for (uint ff = fid; ff < m.faceNumber(); ++ff) {
348 m.face(ff).color() = currentMaterial.color();
355 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
360 if (wids.size() == vids.size()) {
361 if (settings.enableOptionalComponents) {
363 loadedInfo.setFaceWedgeTexCoords();
367 loadedInfo.setFaceWedgeTexCoords();
371 if (loadedInfo.hasFaceWedgeTexCoords()) {
372 if (wids.size() == vids.size()) {
376 for (uint i = 0; i < wids.size(); ++i) {
377 if (wids[i] >= wedgeTexCoords.size()) {
378 throw MalformedFileException(
379 "Bad texcoord index for face " +
380 std::to_string(fid));
384 .cast<
typename FaceType::WedgeTexCoordType::
386 if (currentMaterial.hasTexture) {
387 f.textureIndex() = currentMaterial.mapId;
394 for (uint ff = fid; ff < m.faceNumber(); ++ff) {
395 FaceType& f = m.face(ff);
397 for (uint i = 0; i < f.vertexNumber(); ++i) {
398 uint vid = m.index(f.vertex(i));
400 auto it = std::find(vids.begin(), vids.end(), vid);
401 assert(it != vids.end());
402 uint pos = it - vids.begin();
404 if (wids[pos] >= wedgeTexCoords.size()) {
405 throw MalformedFileException(
406 "Bad texcoord index for face " +
407 std::to_string(fid));
413 .cast<
typename FaceType::WedgeTexCoordType::
415 if (currentMaterial.hasTexture) {
416 f.textureIndex() = currentMaterial.mapId;
426template<EdgeMeshConcept MeshType>
429 MeshInfo& loadedInfo,
430 const Tokenizer& tokens,
431 const ObjMaterial& currentMaterial,
432 const LoadSettings& settings)
434 using EdgeType = MeshType::EdgeType;
437 uint eid = m.addEdge();
438 EdgeType& e = m.edge(eid);
441 Tokenizer::iterator token = tokens.begin();
443 uint vid1 = io::readUInt<uint>(token) - 1;
444 uint vid2 = io::readUInt<uint>(token) - 1;
445 e.setVertices(vid1, vid2);
448 if (HasPerEdgeColor<MeshType>) {
453 if (currentMaterial.hasColor) {
454 if (settings.enableOptionalComponents) {
456 loadedInfo.setEdgeColors();
460 loadedInfo.setEdgeColors();
464 if (loadedInfo.hasEdgeColors()) {
465 if (currentMaterial.hasColor) {
467 m.edge(eid).color() = currentMaterial.color();
492template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
495 std::istream& inputObjStream,
496 const std::vector<std::istream*>& inputMtlStreams,
497 MeshInfo& loadedInfo,
498 const std::string& filename =
"",
499 bool ignoreMtlLib =
false,
501 const LoadSettings& settings = LoadSettings())
506 detail::ObjNormalsMap<MeshType> mapNormalsCache;
510 std::vector<TexCoordIndexedd> texCoords;
513 std::map<std::string, detail::ObjMaterial> materialMap;
516 for (
auto* stream : inputMtlStreams) {
517 detail::loadObjMaterials(materialMap, m, *stream, loadedInfo, settings);
521 detail::ObjMaterial currentMaterial;
523 if constexpr (HasTexturePaths<MeshType>) {
527 if constexpr (HasName<MeshType>) {
531 inputObjStream.seekg(0, inputObjStream.end);
532 std::size_t fsize = inputObjStream.tellg();
533 inputObjStream.seekg(0, inputObjStream.beg);
534 log.startProgress(
"Loading OBJ file", fsize);
539 readAndTokenizeNextNonEmptyLineNoThrow(inputObjStream);
540 if (inputObjStream) {
541 Tokenizer::iterator token = tokens.begin();
542 std::string header = *token++;
543 if (header ==
"mtllib" && !ignoreMtlLib) {
545 std::string mtlfile =
548 detail::loadObjMaterials(
549 materialMap, m, mtlfile, loadedInfo, settings);
551 catch (CannotOpenFileException) {
553 "Cannot open material file " + mtlfile,
554 LogType::WARNING_LOG);
558 if (header ==
"usemtl") {
559 std::string matname = *token;
560 auto it = materialMap.find(matname);
561 if (it != materialMap.end()) {
562 currentMaterial = it->second;
566 "Material " + matname +
" not found.",
567 LogType::WARNING_LOG);
573 detail::readObjVertex(
574 m, token, loadedInfo, tokens, currentMaterial, settings);
577 if constexpr (HasPerVertexNormal<MeshType>) {
578 if (header ==
"vn") {
579 detail::readObjVertexNormal(
580 m, mapNormalsCache, vn, token, loadedInfo, settings);
587 HasPerVertexTexCoord<MeshType> ||
588 HasPerFaceWedgeTexCoords<MeshType>) {
589 if (header ==
"vt") {
592 for (uint i = 0; i < 2; ++i) {
593 tf[i] = io::readDouble<double>(token);
595 if (currentMaterial.hasTexture) {
596 tf.index() = currentMaterial.mapId;
598 texCoords.push_back(tf);
605 if constexpr (HasFaces<MeshType>) {
617 if constexpr (HasEdges<MeshType>) {
620 m, loadedInfo, tokens, currentMaterial, settings);
623 log.progress(inputObjStream.tellg());
625 }
while (inputObjStream);
627 if constexpr (HasPerVertexNormal<MeshType>) {
629 for (
const auto& p : mapNormalsCache) {
630 if (p.first < m.vertexNumber()) {
631 m.vertex(p.first).normal() = p.second;
635 if constexpr (HasPerVertexTexCoord<MeshType>) {
636 using VertexType = MeshType::VertexType;
637 if (!loadedInfo.hasFaceWedgeTexCoords()) {
640 if (texCoords.size() == m.vertexNumber()) {
641 if (settings.enableOptionalComponents) {
642 enableIfPerVertexTexCoordOptional(m);
643 loadedInfo.setVertexTexCoords();
646 if (isPerVertexTexCoordAvailable(m))
647 loadedInfo.setVertexTexCoords();
649 if (loadedInfo.hasVertexTexCoords()) {
651 for (VertexType& v : m.
vertices()) {
654 .cast<
typename VertexType::TexCoordType::
662 if constexpr (HasTextureImages<MeshType>) {
663 if (settings.loadTextureImages) {
664 for (Texture& texture : m.textures()) {
666 texture.image().load(m.meshBasePath() + texture.path());
669 "Cannot load texture " + texture.path(),
670 LogType::WARNING_LOG);
709template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
749template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
797template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
833template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
872template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
926template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
964template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
998template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
Exception thrown when the file cannot be opened.
Definition io.h:58
static std::string fileNameWithoutExtension(const std::string &fullpath)
Get the file name without extension of a file.
Definition file_info.h:217
static std::string pathWithoutFileName(const std::string &fullpath)
Get the path of a file.
Definition file_info.h:197
A simple class that allows to store which elements and their components have been imported/loaded or ...
Definition mesh_info.h:76
A class representing a line segment in n-dimensional space. The class is parameterized by a PointConc...
Definition segment.h:43
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:214
bool enableIfPerEdgeColorOptional(MeshType &m)
If the input mesh has a EdgeContainer, and the Edge Element has a Color Component,...
Definition edge_requirements.h:238
bool enableIfPerFaceWedgeTexCoordsOptional(MeshType &m)
If the input mesh has a FaceContainer, and the Face Element has a WedgeTexCoords Component,...
Definition face_requirements.h:597
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 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
bool enableIfPerFaceColorOptional(MeshType &m)
If the input mesh has a FaceContainer, and the Face Element has a Color Component,...
Definition face_requirements.h:238
void loadObj(MeshType &m, std::istream &inputObjStream, const std::vector< std::istream * > &inputMtlStreams, MeshInfo &loadedInfo, LogType &log=nullLogger, const LoadSettings &settings=LoadSettings())
Loads from the given input obj stream and puts the content into the mesh m.
Definition load.h:710
NullLogger nullLogger
The nullLogger object is an object of type NullLogger that is used as default argument in the functio...
Definition null_logger.h:125
constexpr detail::VerticesView vertices
A view that allows to iterate over the Vertex elements of an object.
Definition vertex.h:60
The LoadSettings structure contains the settings that can be used to load a mesh from a stream/file.
Definition settings.h:35