23#ifndef VCL_IO_MESH_OBJ_LOAD_H
24#define VCL_IO_MESH_OBJ_LOAD_H
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>
33#include <vclib/algorithms/mesh.h>
34#include <vclib/space/complex.h>
35#include <vclib/space/core.h>
44inline void loadObjMaterials(
45 std::map<std::string, ObjMaterial>& materialMap,
51 auto manageMaterialArgsFn = [&](
auto& token) {
52 while ((*token)[0] ==
'-') {
53 if (*token ==
"-o" || *token ==
"-s" || *token ==
"-t") {
60 if (*token ==
"-mm") {
66 if (*token ==
"-blendu" || *token ==
"-blendv" || *token ==
"-cc" ||
67 *token ==
"-clamp" || *token ==
"-texres") {
76 Tokenizer tokens = readAndTokenizeNextNonEmptyLineNoThrow(stream);
80 Tokenizer::iterator token = tokens.begin();
81 std::string header = *token++;
82 if (header ==
"newmtl") {
84 materialMap[matName] = mat;
87 mat.matName = matName;
90 if (tokens.size() >= 4) {
91 if (*token !=
"spectral" && *token !=
"xyz") {
92 mat.Ka.x() = io::readFloat<float>(token);
93 mat.Ka.y() = io::readFloat<float>(token);
94 mat.Ka.z() = io::readFloat<float>(token);
99 if (tokens.size() >= 4) {
100 if (*token !=
"spectral" && *token !=
"xyz") {
101 mat.Kd.x() = io::readFloat<float>(token);
102 mat.Kd.y() = io::readFloat<float>(token);
103 mat.Kd.z() = io::readFloat<float>(token);
107 if (header ==
"Ks") {
108 if (tokens.size() >= 4) {
109 if (*token !=
"spectral" && *token !=
"xyz") {
110 mat.Ks.x() = io::readFloat<float>(token);
111 mat.Ks.y() = io::readFloat<float>(token);
112 mat.Ks.z() = io::readFloat<float>(token);
116 if (header ==
"Ke") {
117 if (tokens.size() >= 4) {
118 if (*token !=
"spectral" && *token !=
"xyz") {
119 mat.Ke.x() = io::readFloat<float>(token);
120 mat.Ke.y() = io::readFloat<float>(token);
121 mat.Ke.z() = io::readFloat<float>(token);
126 if ((*token)[0] ==
'-')
128 mat.d = io::readFloat<float>(token);
130 if (header ==
"Tr") {
131 if ((*token)[0] ==
'-')
133 mat.d = 1 - io::readFloat<float>(token);
135 if (header ==
"Ns") {
136 mat.Ns = io::readFloat<float>(token);
138 if (header ==
"illum") {
139 mat.illum = io::readFloat<int>(token);
141 if (header ==
"map_Kd") {
143 manageMaterialArgsFn(token);
146 std::ranges::replace(mat.map_Kd,
'\\',
'/');
148 if (header ==
"map_Ke") {
150 manageMaterialArgsFn(token);
153 std::ranges::replace(mat.map_Ke,
'\\',
'/');
155 if (header ==
"map_bump" || header ==
"bump") {
157 manageMaterialArgsFn(token);
158 mat.map_bump = *token;
160 std::ranges::replace(mat.map_bump,
'\\',
'/');
164 if (!matName.empty())
165 materialMap[matName] = mat;
168template<MeshConcept MeshType>
169void loadObjMaterials(
170 std::map<std::string, ObjMaterial>& materialMap,
172 std::istream& stream,
173 MeshInfo& loadedInfo,
174 const LoadSettings& settings)
176 loadObjMaterials(materialMap, stream);
178 for (
auto& [matName, mat] : materialMap) {
179 if constexpr (HasMaterials<MeshType>) {
180 loadedInfo.setMaterials();
181 Material m = mat.toMaterial();
182 mat.matId = mesh.materialsNumber();
183 mesh.pushMaterial(m);
188template<MeshConcept MeshType>
189void loadObjMaterials(
190 std::map<std::string, ObjMaterial>& materialMap,
192 const std::string& mtllib,
193 MeshInfo& loadedInfo,
194 const LoadSettings& settings)
196 std::ifstream file = openInputFileStream(mtllib);
197 loadObjMaterials(materialMap, mesh, file, loadedInfo, settings);
200template<MeshConcept MeshType>
203 Tokenizer::iterator& token,
204 MeshInfo& loadedInfo,
205 const Tokenizer& tokens,
206 const LoadSettings& settings)
208 uint vid = m.addVertex();
209 for (uint i = 0; i < 3; ++i) {
210 m.vertex(vid).position()[i] = io::readDouble<double>(token);
212 if constexpr (HasPerVertexColor<MeshType>) {
216 if (tokens.size() > 6) {
217 if (settings.enableOptionalComponents) {
218 enableIfPerVertexColorOptional(m);
219 loadedInfo.setPerVertexColor();
222 if (isPerVertexColorAvailable(m))
223 loadedInfo.setPerVertexColor();
227 if (loadedInfo.hasPerVertexColor()) {
230 if (tokens.size() > 6) {
231 m.vertex(vid).color().setRedF(io::readFloat<float>(token));
232 m.vertex(vid).color().setGreenF(io::readFloat<float>(token));
233 m.vertex(vid).color().setBlueF(io::readFloat<float>(token));
239template<FaceMeshConcept MeshType>
242 MeshInfo& loadedInfo,
243 const Tokenizer& tokens,
244 const std::vector<TexCoordd>& wedgeTexCoords,
245 const ObjMaterial& currentMaterial,
246 const LoadSettings& settings)
248 using FaceType = MeshType::FaceType;
250 std::vector<uint> vids;
251 std::vector<uint> wids;
253 loadedInfo.updateMeshType(tokens.size() - 1);
256 Tokenizer::iterator token = tokens.begin();
258 vids.resize(tokens.size() - 1);
259 wids.reserve(tokens.size() - 1);
260 for (uint i = 0; i < tokens.size() - 1; ++i) {
261 Tokenizer subt(*token,
'/',
false);
262 auto t = subt.begin();
263 vids[i] = io::readUInt<uint>(t) - 1;
264 if (subt.size() > 1) {
266 wids.push_back(io::readUInt<uint>(t) - 1);
273 uint fid = m.addFace();
274 FaceType& f = m.face(fid);
277 bool splitFace =
false;
279 if constexpr (FaceType::VERTEX_NUMBER < 0) {
281 f.resizeVertices(tokens.size() - 1);
283 else if (FaceType::VERTEX_NUMBER != tokens.size() - 1) {
292 for (uint i = 0; i < vids.size(); ++i) {
293 if (vids[i] >= m.vertexNumber()) {
294 throw MalformedFileException(
295 "Bad vertex index for face " + std::to_string(fid));
297 f.setVertex(i, vids[i]);
301 addTriangleFacesFromPolygon(m, f, vids);
305 if constexpr (HasPerFaceMaterialIndex<MeshType>) {
306 if (fid == 0 && currentMaterial.matId !=
UINT_NULL) {
307 if (settings.enableOptionalComponents) {
309 loadedInfo.setPerFaceMaterialIndex();
313 loadedInfo.setPerFaceMaterialIndex();
317 if (loadedInfo.hasPerFaceMaterialIndex()) {
319 f.materialIndex() = currentMaterial.matId;
322 for (uint ff = fid; ff < m.faceNumber(); ++ff) {
323 FaceType& f = m.face(ff);
324 f.materialIndex() = currentMaterial.matId;
331 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
336 if (wids.size() == vids.size()) {
337 if (settings.enableOptionalComponents) {
339 loadedInfo.setPerFaceWedgeTexCoords();
343 loadedInfo.setPerFaceWedgeTexCoords();
348 if (loadedInfo.hasPerFaceWedgeTexCoords()) {
349 if (wids.size() == vids.size()) {
353 for (uint i = 0; i < wids.size(); ++i) {
354 if (wids[i] >= wedgeTexCoords.size()) {
355 throw MalformedFileException(
356 "Bad texcoord index for face " +
357 std::to_string(fid));
360 wedgeTexCoords[wids[i]]
361 .cast<
typename FaceType::WedgeTexCoordType::
368 for (uint ff = fid; ff < m.faceNumber(); ++ff) {
369 FaceType& f = m.face(ff);
371 for (uint i = 0; i < f.vertexNumber(); ++i) {
372 uint vid = m.index(f.vertex(i));
374 auto it = std::find(vids.begin(), vids.end(), vid);
375 assert(it != vids.end());
376 uint pos = it - vids.begin();
378 if (wids[pos] >= wedgeTexCoords.size()) {
379 throw MalformedFileException(
380 "Bad texcoord index for face " +
381 std::to_string(fid));
386 wedgeTexCoords[wids[pos]]
387 .cast<
typename FaceType::WedgeTexCoordType::
397template<EdgeMeshConcept MeshType>
400 MeshInfo& loadedInfo,
401 const Tokenizer& tokens,
402 const ObjMaterial& currentMaterial,
403 const LoadSettings& settings)
405 using EdgeType = MeshType::EdgeType;
408 uint eid = m.addEdge();
409 EdgeType& e = m.edge(eid);
412 Tokenizer::iterator token = tokens.begin();
414 uint vid1 = io::readUInt<uint>(token) - 1;
415 uint vid2 = io::readUInt<uint>(token) - 1;
416 e.setVertices(vid1, vid2);
438template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
441 std::istream& inputObjStream,
442 const std::vector<std::istream*>& inputMtlStreams,
443 MeshInfo& loadedInfo,
444 const std::string& filename =
"",
445 bool ignoreMtlLib =
false,
446 const LoadSettings& settings = LoadSettings(),
452 std::vector<Point3d> normals;
456 std::vector<TexCoordd> texCoords;
459 std::map<std::string, detail::ObjMaterial> materialMap;
462 for (
auto* stream : inputMtlStreams) {
463 detail::loadObjMaterials(materialMap, m, *stream, loadedInfo, settings);
467 detail::ObjMaterial currentMaterial;
469 if constexpr (HasMaterials<MeshType>) {
473 if constexpr (HasName<MeshType>) {
477 inputObjStream.seekg(0, inputObjStream.end);
478 std::size_t fsize = inputObjStream.tellg();
479 inputObjStream.seekg(0, inputObjStream.beg);
480 log.startProgress(
"Loading OBJ file", fsize);
485 readAndTokenizeNextNonEmptyLineNoThrow(inputObjStream);
486 if (inputObjStream) {
487 Tokenizer::iterator token = tokens.begin();
488 std::string header = *token++;
489 if (header ==
"mtllib" && !ignoreMtlLib) {
491 std::string mtlfile =
494 detail::loadObjMaterials(
495 materialMap, m, mtlfile, loadedInfo, settings);
497 catch (CannotOpenFileException) {
499 "Cannot open material file " + mtlfile,
500 LogType::WARNING_LOG);
504 if (header ==
"usemtl") {
505 std::string matname = *token;
506 auto it = materialMap.find(matname);
507 if (it != materialMap.end()) {
508 currentMaterial = it->second;
512 "Material " + matname +
" not found.",
513 LogType::WARNING_LOG);
519 loadedInfo.setVertices();
520 loadedInfo.setPerVertexPosition();
521 detail::readObjVertex(m, token, loadedInfo, tokens, settings);
525 if (header ==
"vn") {
526 if constexpr (HasPerVertexNormal<MeshType>) {
528 for (uint i = 0; i < 3; ++i) {
529 n[i] = io::readDouble<double>(token);
531 normals.push_back(n);
536 if (header ==
"vt") {
538 HasPerVertexTexCoord<MeshType> ||
539 HasPerFaceWedgeTexCoords<MeshType>) {
542 for (uint i = 0; i < 2; ++i) {
543 tf[i] = io::readDouble<double>(token);
545 texCoords.push_back(tf);
553 loadedInfo.setFaces();
554 loadedInfo.setPerFaceVertexReferences();
555 if constexpr (HasFaces<MeshType>) {
567 loadedInfo.setEdges();
568 loadedInfo.setPerEdgeVertexReferences();
569 if constexpr (HasEdges<MeshType>) {
571 m, loadedInfo, tokens, currentMaterial, settings);
574 log.progress(inputObjStream.tellg());
576 }
while (inputObjStream);
578 if constexpr (HasPerVertexNormal<MeshType>) {
579 using NormalType =
typename MeshType::VertexType::NormalType;
580 using NST =
typename NormalType::ScalarType;
581 if (settings.enableOptionalComponents) {
582 enableIfPerVertexNormalOptional(m);
583 loadedInfo.setPerVertexNormal();
586 if (isPerVertexNormalAvailable(m))
587 loadedInfo.setPerVertexNormal();
590 if (loadedInfo.hasPerVertexNormal()) {
591 for (uint i = 0;
const auto& n : normals) {
592 if (i < m.vertexNumber()) {
593 m.vertex(i).normal() = n.template cast<NST>();
600 if constexpr (HasPerVertexTexCoord<MeshType>) {
601 if (!loadedInfo.hasPerFaceWedgeTexCoords()) {
602 if (texCoords.
size() == m.vertexNumber()) {
604 if (settings.enableOptionalComponents) {
605 enableIfPerVertexTexCoordOptional(m);
606 loadedInfo.setPerVertexTexCoord();
609 if (isPerVertexTexCoordAvailable(m))
610 loadedInfo.setPerVertexTexCoord();
612 if (loadedInfo.hasPerVertexTexCoord()) {
614 typename MeshType::VertexType::TexCoordType;
615 using TCT =
typename TexCoordType::ScalarType;
616 for (uint i = 0; i < m.vertexNumber(); ++i) {
617 m.vertex(i).texCoord() =
618 texCoords[i].template cast<TCT>();
623 if constexpr (HasMaterials<MeshType>) {
624 if (m.materialsNumber() > 0) {
625 if (settings.enableOptionalComponents) {
626 enableIfPerVertexMaterialIndexOptional(m);
627 loadedInfo.setPerVertexMaterialIndex();
630 if (isPerVertexMaterialIndexAvailable(m))
631 loadedInfo.setPerVertexMaterialIndex();
633 if (loadedInfo.hasPerVertexMaterialIndex()) {
634 for (uint i = 0; i < m.vertexNumber(); ++i) {
636 m.vertex(i).materialIndex() = 0;
645 if constexpr (HasMaterials<MeshType>) {
646 if (settings.loadTextureImages) {
648 loadTextureImages(m,
"", {BASE_COLOR, EMISSIVE}, log);
685template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
732template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
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
TextureType
Defines the types of textures used in the PBR material model.
Definition material.h:62
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
constexpr uint UINT_NULL
The UINT_NULL value represent a null value of uint that is the maximum value that can be represented ...
Definition base.h:48
TexCoord< double, ElementType, OPT > TexCoordd
Definition tex_coord.h:209
bool enableIfPerFaceWedgeTexCoordsOptional(MeshType &m)
If the input mesh has a FaceContainer, and the Face Element has a WedgeTexCoords Component,...
Definition face_requirements.h:936
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:910
bool isPerFaceMaterialIndexAvailable(const MeshType &m)
Returns true if the MaterialIndex component is available (enabled) in the Face element of the input m...
Definition face_requirements.h:608
bool enableIfPerFaceMaterialIndexOptional(MeshType &m)
If the input mesh has a FaceContainer, and the Face Element has a MaterialIndex Component,...
Definition face_requirements.h:633
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:686
Point3< double > Point3d
A convenience alias for a 3-dimensional Point with double-precision floating-point components.
Definition point.h:780
The LoadSettings structure contains the settings that can be used to load a mesh from a stream/file.
Definition settings.h:35