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.materialCount();
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<Point3d>& normals,
245 const std::vector<TexCoordd>& wedgeTexCoords,
246 const ObjMaterial& currentMaterial,
247 const LoadSettings& settings)
249 using FaceType = MeshType::FaceType;
251 std::vector<uint> vids;
252 std::vector<uint> wids;
253 std::vector<uint> nids;
255 loadedInfo.updateMeshType(tokens.size() - 1);
258 Tokenizer::iterator token = tokens.begin();
260 vids.resize(tokens.size() - 1);
261 wids.reserve(tokens.size() - 1);
262 nids.reserve(tokens.size() - 1);
263 for (uint i = 0; i < tokens.size() - 1; ++i) {
264 Tokenizer subt(*token,
'/',
false);
265 auto t = subt.begin();
266 vids[i] = io::readUInt<uint>(t) - 1;
267 if (subt.size() > 1) {
269 wids.push_back(io::readUInt<uint>(t) - 1);
271 if (subt.size() > 2) {
273 nids.push_back(io::readUInt<uint>(t) - 1);
281 uint fid = m.addFace();
282 FaceType& f = m.face(fid);
285 bool splitFace =
false;
287 if constexpr (FaceType::VERTEX_COUNT < 0) {
289 f.resizeVertices(tokens.size() - 1);
291 else if (FaceType::VERTEX_COUNT != tokens.size() - 1) {
300 for (uint i = 0; i < vids.size(); ++i) {
301 if (vids[i] >= m.vertexCount()) {
302 throw MalformedFileException(
303 "Bad vertex index for face " + std::to_string(fid));
305 f.setVertex(i, vids[i]);
309 addTriangleFacesFromPolygon(m, f, vids);
313 if constexpr (HasPerFaceMaterialIndex<MeshType>) {
314 if (fid == 0 && currentMaterial.matId !=
UINT_NULL) {
315 if (settings.enableOptionalComponents) {
317 loadedInfo.setPerFaceMaterialIndex();
321 loadedInfo.setPerFaceMaterialIndex();
325 if (loadedInfo.hasPerFaceMaterialIndex()) {
327 f.materialIndex() = currentMaterial.matId;
330 for (uint ff = fid; ff < m.faceCount(); ++ff) {
331 FaceType& f = m.face(ff);
332 f.materialIndex() = currentMaterial.matId;
339 if constexpr (HasPerVertexNormal<MeshType>) {
340 using NormalType =
typename MeshType::VertexType::NormalType;
341 using NST =
typename NormalType::ScalarType;
346 if (nids.size() == vids.size()) {
347 if (settings.enableOptionalComponents) {
348 enableIfPerVertexNormalOptional(m);
349 loadedInfo.setPerVertexNormal();
352 if (isPerVertexNormalAvailable(m)) {
353 loadedInfo.setPerVertexNormal();
358 if (loadedInfo.hasPerVertexNormal()) {
359 if (nids.size() == vids.size()) {
360 for (uint i = 0; i < nids.size(); ++i) {
361 if (nids[i] >= normals.size()) {
362 throw MalformedFileException(
363 "Bad normal index for face " + std::to_string(fid));
365 m.vertex(vids[i]).normal() = normals[nids[i]].cast<NST>();
372 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
373 using WedgeTCType = MeshType::FaceType::WedgeTexCoordType;
374 using WTCST =
typename WedgeTCType::ScalarType;
379 if (wids.size() == vids.size()) {
380 if (settings.enableOptionalComponents) {
382 loadedInfo.setPerFaceWedgeTexCoords();
386 loadedInfo.setPerFaceWedgeTexCoords();
391 if (loadedInfo.hasPerFaceWedgeTexCoords()) {
392 if (wids.size() == vids.size()) {
396 for (uint i = 0; i < wids.size(); ++i) {
397 if (wids[i] >= wedgeTexCoords.size()) {
398 throw MalformedFileException(
399 "Bad texcoord index for face " +
400 std::to_string(fid));
403 wedgeTexCoords[wids[i]].cast<WTCST>();
409 for (uint ff = fid; ff < m.faceCount(); ++ff) {
410 FaceType& f = m.face(ff);
412 for (uint i = 0; i < f.vertexCount(); ++i) {
413 uint vid = m.index(f.vertex(i));
415 auto it = std::find(vids.begin(), vids.end(), vid);
416 assert(it != vids.end());
417 uint pos = it - vids.begin();
419 if (wids[pos] >= wedgeTexCoords.size()) {
420 throw MalformedFileException(
421 "Bad texcoord index for face " +
422 std::to_string(fid));
427 wedgeTexCoords[wids[pos]]
428 .cast<
typename FaceType::WedgeTexCoordType::
438template<EdgeMeshConcept MeshType>
441 MeshInfo& loadedInfo,
442 const Tokenizer& tokens,
443 const ObjMaterial& currentMaterial,
444 const LoadSettings& settings)
446 using EdgeType = MeshType::EdgeType;
449 uint eid = m.addEdge();
450 EdgeType& e = m.edge(eid);
453 Tokenizer::iterator token = tokens.begin();
455 uint vid1 = io::readUInt<uint>(token) - 1;
456 uint vid2 = io::readUInt<uint>(token) - 1;
457 e.setVertices(vid1, vid2);
479template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
482 std::istream& inputObjStream,
483 const std::vector<std::istream*>& inputMtlStreams,
484 MeshInfo& loadedInfo,
485 const std::string& filename =
"",
486 bool ignoreMtlLib =
false,
487 const LoadSettings& settings = LoadSettings(),
493 std::vector<Point3d> normals;
497 std::vector<TexCoordd> texCoords;
500 std::map<std::string, detail::ObjMaterial> materialMap;
503 for (
auto* stream : inputMtlStreams) {
504 detail::loadObjMaterials(materialMap, m, *stream, loadedInfo, settings);
508 detail::ObjMaterial currentMaterial;
510 if constexpr (HasMaterials<MeshType>) {
514 if constexpr (HasName<MeshType>) {
518 inputObjStream.seekg(0, inputObjStream.end);
519 std::size_t fsize = inputObjStream.tellg();
520 inputObjStream.seekg(0, inputObjStream.beg);
521 log.startProgress(
"Loading OBJ file", fsize);
526 readAndTokenizeNextNonEmptyLineNoThrow(inputObjStream);
527 if (inputObjStream) {
528 Tokenizer::iterator token = tokens.begin();
529 std::string header = *token++;
530 if (header ==
"mtllib" && !ignoreMtlLib) {
532 std::string mtlfile =
535 detail::loadObjMaterials(
536 materialMap, m, mtlfile, loadedInfo, settings);
538 catch (CannotOpenFileException) {
540 "Cannot open material file " + mtlfile,
541 LogType::WARNING_LOG);
545 if (header ==
"usemtl") {
546 std::string matname = *token;
547 auto it = materialMap.find(matname);
548 if (it != materialMap.end()) {
549 currentMaterial = it->second;
553 "Material " + matname +
" not found.",
554 LogType::WARNING_LOG);
560 loadedInfo.setVertices();
561 loadedInfo.setPerVertexPosition();
562 detail::readObjVertex(m, token, loadedInfo, tokens, settings);
566 if (header ==
"vn") {
567 if constexpr (HasPerVertexNormal<MeshType>) {
569 for (uint i = 0; i < 3; ++i) {
570 n[i] = io::readDouble<double>(token);
572 normals.push_back(n);
577 if (header ==
"vt") {
579 HasPerVertexTexCoord<MeshType> ||
580 HasPerFaceWedgeTexCoords<MeshType>) {
583 for (uint i = 0; i < 2; ++i) {
584 tf[i] = io::readDouble<double>(token);
586 texCoords.push_back(tf);
594 loadedInfo.setFaces();
595 loadedInfo.setPerFaceVertexReferences();
596 if constexpr (HasFaces<MeshType>) {
609 loadedInfo.setEdges();
610 loadedInfo.setPerEdgeVertexReferences();
611 if constexpr (HasEdges<MeshType>) {
613 m, loadedInfo, tokens, currentMaterial, settings);
616 log.progress(inputObjStream.tellg());
618 }
while (inputObjStream);
620 if constexpr (HasPerVertexNormal<MeshType>) {
621 using NormalType =
typename MeshType::VertexType::NormalType;
622 using NST =
typename NormalType::ScalarType;
624 if (!loadedInfo.hasPerVertexNormal()) {
625 if (normals.size() == m.vertexCount()) {
626 if (settings.enableOptionalComponents) {
627 enableIfPerVertexNormalOptional(m);
628 loadedInfo.setPerVertexNormal();
631 if (isPerVertexNormalAvailable(m))
632 loadedInfo.setPerVertexNormal();
635 if (loadedInfo.hasPerVertexNormal()) {
636 for (uint i = 0;
const auto& n : normals) {
637 m.vertex(i).normal() = n.template cast<NST>();
645 if constexpr (HasPerVertexTexCoord<MeshType>) {
646 if (!loadedInfo.hasPerFaceWedgeTexCoords()) {
647 if (texCoords.size() == m.vertexCount()) {
649 if (settings.enableOptionalComponents) {
650 enableIfPerVertexTexCoordOptional(m);
651 loadedInfo.setPerVertexTexCoord();
654 if (isPerVertexTexCoordAvailable(m))
655 loadedInfo.setPerVertexTexCoord();
657 if (loadedInfo.hasPerVertexTexCoord()) {
659 typename MeshType::VertexType::TexCoordType;
660 using TCT =
typename TexCoordType::ScalarType;
661 for (uint i = 0; i < m.vertexCount(); ++i) {
662 m.vertex(i).texCoord() =
663 texCoords[i].template cast<TCT>();
668 if constexpr (HasMaterials<MeshType>) {
669 if (m.materialCount() > 0) {
670 if (settings.enableOptionalComponents) {
671 enableIfPerVertexMaterialIndexOptional(m);
672 loadedInfo.setPerVertexMaterialIndex();
675 if (isPerVertexMaterialIndexAvailable(m))
676 loadedInfo.setPerVertexMaterialIndex();
678 if (loadedInfo.hasPerVertexMaterialIndex()) {
679 for (uint i = 0; i < m.vertexCount(); ++i) {
681 m.vertex(i).materialIndex() = 0;
690 if constexpr (HasMaterials<MeshType>) {
691 if (settings.loadTextureImages) {
693 loadTextureImages(m, m.meshBasePath(), {BASE_COLOR, EMISSIVE}, log);
730template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
777template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
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
A class representing a line segment in n-dimensional space. The class is parameterized by a PointConc...
Definition segment.h:41
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:49
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:731
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