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>
44template<MeshConcept MeshType>
45using ObjNormalsMap = std::conditional_t<
46 HasPerVertexNormal<MeshType>,
47 std::map<uint, typename MeshType::VertexType::NormalType>,
48 std::map<uint, Point3d>>;
50template<MeshConcept MeshType>
52 std::map<std::string, ObjMaterial>& materialMap,
56 const LoadSettings& settings)
62 Tokenizer tokens = readAndTokenizeNextNonEmptyLineNoThrow(stream);
66 Tokenizer::iterator token = tokens.begin();
67 std::string header = *token++;
68 if (header ==
"newmtl") {
70 materialMap[matName] = mat;
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);
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);
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);
103 if ((*token)[0] ==
'-')
105 mat.d = io::readFloat<float>(token);
107 if (header ==
"Tr") {
108 if ((*token)[0] ==
'-')
110 mat.d = 1 - io::readFloat<float>(token);
112 if (header ==
"Ns") {
113 mat.Ns = io::readFloat<float>(token);
115 if (header ==
"illum") {
116 mat.illum = io::readFloat<int>(token);
118 if (header ==
"map_Kd") {
120 while ((*token)[0] ==
'-') {
121 if (*token ==
"-o" || *token ==
"-s" || *token ==
"-t") {
128 if (*token ==
"-mm") {
134 if (*token ==
"-blendu" || *token ==
"-blendv" ||
135 *token ==
"-cc" || *token ==
"-clamp" ||
136 *token ==
"-texres") {
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);
157 if (!matName.empty())
158 materialMap[matName] = mat;
161template<MeshConcept MeshType>
162void loadObjMaterials(
163 std::map<std::string, ObjMaterial>& materialMap,
165 const std::string& mtllib,
166 MeshInfo& loadedInfo,
167 const LoadSettings& settings)
169 std::ifstream file = openInputFileStream(mtllib);
170 loadObjMaterials(materialMap, mesh, file, loadedInfo, settings);
173template<MeshConcept MeshType>
176 Tokenizer::iterator& token,
177 MeshInfo& loadedInfo,
178 const Tokenizer& tokens,
179 const ObjMaterial& currentMaterial,
180 const LoadSettings& settings)
182 uint vid = m.addVertex();
183 for (uint i = 0; i < 3; ++i) {
184 m.vertex(vid).position()[i] = io::readDouble<double>(token);
186 if constexpr (HasPerVertexColor<MeshType>) {
191 if (currentMaterial.hasColor || tokens.size() > 6) {
192 if (settings.enableOptionalComponents) {
193 enableIfPerVertexColorOptional(m);
194 loadedInfo.setPerVertexColor();
197 if (isPerVertexColorAvailable(m))
198 loadedInfo.setPerVertexColor();
202 if (loadedInfo.hasPerVertexColor()) {
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));
210 else if (currentMaterial.hasColor) {
211 m.vertex(vid).color() = currentMaterial.color();
217template<MeshConcept MeshType>
218void readObjVertexNormal(
220 detail::ObjNormalsMap<MeshType>& mapNormalsCache,
222 Tokenizer::iterator& token,
223 MeshInfo& loadedInfo,
224 const LoadSettings& settings)
226 using NormalType = MeshType::VertexType::NormalType;
230 if (settings.enableOptionalComponents) {
231 enableIfPerVertexNormalOptional(m);
232 loadedInfo.setPerVertexNormal();
235 if (isPerVertexNormalAvailable(m))
236 loadedInfo.setPerVertexNormal();
239 if (loadedInfo.hasPerVertexNormal()) {
242 for (uint i = 0; i < 3; ++i) {
243 n[i] = io::readDouble<typename NormalType::ScalarType>(token);
246 if (m.vertexNumber() > vn) {
247 m.vertex(vn).normal() = n;
252 mapNormalsCache[vn] = n;
257template<FaceMeshConcept MeshType>
260 MeshInfo& loadedInfo,
261 const Tokenizer& tokens,
262 const std::vector<TexCoordIndexedd>& wedgeTexCoords,
263 const ObjMaterial& currentMaterial,
264 const LoadSettings& settings)
266 using FaceType = MeshType::FaceType;
268 std::vector<uint> vids;
269 std::vector<uint> wids;
271 loadedInfo.updateMeshType(tokens.size() - 1);
274 Tokenizer::iterator token = tokens.begin();
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) {
284 wids.push_back(io::readUInt<uint>(t) - 1);
291 uint fid = m.addFace();
292 FaceType& f = m.face(fid);
295 bool splitFace =
false;
297 if constexpr (FaceType::VERTEX_NUMBER < 0) {
299 f.resizeVertices(tokens.size() - 1);
301 else if (FaceType::VERTEX_NUMBER != tokens.size() - 1) {
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));
315 f.setVertex(i, vids[i]);
319 addTriangleFacesFromPolygon(m, f, vids);
323 if (HasPerFaceColor<MeshType>) {
328 if (currentMaterial.hasColor) {
329 if (settings.enableOptionalComponents) {
331 loadedInfo.setPerFaceColor();
335 loadedInfo.setPerFaceColor();
339 if (loadedInfo.hasPerFaceColor()) {
340 if (currentMaterial.hasColor) {
343 for (uint ff = fid; ff < m.faceNumber(); ++ff) {
344 m.face(ff).color() = currentMaterial.color();
351 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
356 if (wids.size() == vids.size()) {
357 if (settings.enableOptionalComponents) {
359 loadedInfo.setPerFaceWedgeTexCoords();
363 loadedInfo.setPerFaceWedgeTexCoords();
367 if (loadedInfo.hasPerFaceWedgeTexCoords()) {
368 if (wids.size() == vids.size()) {
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));
380 .cast<
typename FaceType::WedgeTexCoordType::
382 if (currentMaterial.hasTexture) {
383 f.textureIndex() = currentMaterial.mapId;
390 for (uint ff = fid; ff < m.faceNumber(); ++ff) {
391 FaceType& f = m.face(ff);
393 for (uint i = 0; i < f.vertexNumber(); ++i) {
394 uint vid = m.index(f.vertex(i));
396 auto it = std::find(vids.begin(), vids.end(), vid);
397 assert(it != vids.end());
398 uint pos = it - vids.begin();
400 if (wids[pos] >= wedgeTexCoords.size()) {
401 throw MalformedFileException(
402 "Bad texcoord index for face " +
403 std::to_string(fid));
409 .cast<
typename FaceType::WedgeTexCoordType::
411 if (currentMaterial.hasTexture) {
412 f.textureIndex() = currentMaterial.mapId;
422template<EdgeMeshConcept MeshType>
425 MeshInfo& loadedInfo,
426 const Tokenizer& tokens,
427 const ObjMaterial& currentMaterial,
428 const LoadSettings& settings)
430 using EdgeType = MeshType::EdgeType;
433 uint eid = m.addEdge();
434 EdgeType& e = m.edge(eid);
437 Tokenizer::iterator token = tokens.begin();
439 uint vid1 = io::readUInt<uint>(token) - 1;
440 uint vid2 = io::readUInt<uint>(token) - 1;
441 e.setVertices(vid1, vid2);
444 if (HasPerEdgeColor<MeshType>) {
449 if (currentMaterial.hasColor) {
450 if (settings.enableOptionalComponents) {
452 loadedInfo.setPerEdgeColor();
456 loadedInfo.setPerEdgeColor();
460 if (loadedInfo.hasPerEdgeColor()) {
461 if (currentMaterial.hasColor) {
463 m.edge(eid).color() = currentMaterial.color();
488template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
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(),
502 detail::ObjNormalsMap<MeshType> mapNormalsCache;
506 std::vector<TexCoordIndexedd> texCoords;
509 std::map<std::string, detail::ObjMaterial> materialMap;
512 for (
auto* stream : inputMtlStreams) {
513 detail::loadObjMaterials(materialMap, m, *stream, loadedInfo, settings);
517 detail::ObjMaterial currentMaterial;
519 if constexpr (HasTexturePaths<MeshType>) {
523 if constexpr (HasName<MeshType>) {
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);
535 readAndTokenizeNextNonEmptyLineNoThrow(inputObjStream);
536 if (inputObjStream) {
537 Tokenizer::iterator token = tokens.begin();
538 std::string header = *token++;
539 if (header ==
"mtllib" && !ignoreMtlLib) {
541 std::string mtlfile =
544 detail::loadObjMaterials(
545 materialMap, m, mtlfile, loadedInfo, settings);
547 catch (CannotOpenFileException) {
549 "Cannot open material file " + mtlfile,
550 LogType::WARNING_LOG);
554 if (header ==
"usemtl") {
555 std::string matname = *token;
556 auto it = materialMap.find(matname);
557 if (it != materialMap.end()) {
558 currentMaterial = it->second;
562 "Material " + matname +
" not found.",
563 LogType::WARNING_LOG);
569 loadedInfo.setVertices();
570 loadedInfo.setPerVertexPosition();
571 detail::readObjVertex(
572 m, token, loadedInfo, tokens, currentMaterial, settings);
575 if (header ==
"vn") {
576 loadedInfo.setPerVertexNormal();
577 if constexpr (HasPerVertexNormal<MeshType>) {
578 detail::readObjVertexNormal(
579 m, mapNormalsCache, vn, token, loadedInfo, settings);
586 HasPerVertexTexCoord<MeshType> ||
587 HasPerFaceWedgeTexCoords<MeshType>) {
588 if (header ==
"vt") {
591 for (uint i = 0; i < 2; ++i) {
592 tf[i] = io::readDouble<double>(token);
594 if (currentMaterial.hasTexture) {
595 tf.index() = currentMaterial.mapId;
597 texCoords.push_back(tf);
605 loadedInfo.setFaces();
606 loadedInfo.setPerFaceVertexReferences();
607 if constexpr (HasFaces<MeshType>) {
619 loadedInfo.setEdges();
620 loadedInfo.setPerEdgeVertexReferences();
621 if constexpr (HasEdges<MeshType>) {
623 m, loadedInfo, tokens, currentMaterial, settings);
626 log.progress(inputObjStream.tellg());
628 }
while (inputObjStream);
630 if constexpr (HasPerVertexNormal<MeshType>) {
632 for (
const auto& p : mapNormalsCache) {
633 if (p.first < m.vertexNumber()) {
634 m.vertex(p.first).normal() = p.second;
638 if constexpr (HasPerVertexTexCoord<MeshType>) {
639 using VertexType = MeshType::VertexType;
640 if (!loadedInfo.hasPerFaceWedgeTexCoords()) {
643 if (texCoords.
size() == m.vertexNumber()) {
644 if (settings.enableOptionalComponents) {
645 enableIfPerVertexTexCoordOptional(m);
646 loadedInfo.setPerVertexTexCoord();
649 if (isPerVertexTexCoordAvailable(m))
650 loadedInfo.setPerVertexTexCoord();
652 if (loadedInfo.hasPerVertexTexCoord()) {
654 for (VertexType& v : m.
vertices()) {
657 .cast<
typename VertexType::TexCoordType::
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()) {
671 "Cannot load texture " + texture.path(),
672 LogType::WARNING_LOG);
711template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
758template<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
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