Visual Computing Library
Loading...
Searching...
No Matches
save.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_LOAD_SAVE_OBJ_SAVE_H
24#define VCL_LOAD_SAVE_OBJ_SAVE_H
25
26#include "material.h"
27
28#include <vclib/exceptions/io.h>
29#include <vclib/io/file_info.h>
30#include <vclib/io/write.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>
35
36#include <map>
37
38namespace vcl {
39
40namespace detail {
41
42template<VertexConcept VertexType, MeshConcept MeshType>
43ObjMaterial objMaterialFromVertex(
44 const VertexType& v,
45 const MeshType& m,
46 const MeshInfo& fi)
47{
48 ObjMaterial mat;
49 if constexpr (HasPerVertexColor<MeshType>) {
50 if (fi.hasVertexColors()) {
51 mat.hasColor = true;
52 mat.Kd.x() = v.color().redF();
53 mat.Kd.y() = v.color().greenF();
54 mat.Kd.z() = v.color().blueF();
55 }
56 }
57 if constexpr (HasPerVertexTexCoord<MeshType>) {
58 if (fi.hasVertexTexCoords()) {
59 mat.hasTexture = true;
60 if constexpr (HasTexturePaths<MeshType>) {
61 mat.map_Kd = m.texturePath(v.texCoord().index());
62 }
63 }
64 }
65 return mat;
66}
67
68template<FaceConcept FaceType, MeshConcept MeshType>
69ObjMaterial objMaterialFromFace(
70 const FaceType& f,
71 const MeshType& m,
72 const MeshInfo& fi)
73{
74 ObjMaterial mat;
75 if constexpr (HasPerFaceColor<MeshType>) {
76 if (fi.hasFaceColors()) {
77 mat.hasColor = true;
78 mat.Kd.x() = f.color().redF();
79 mat.Kd.y() = f.color().greenF();
80 mat.Kd.z() = f.color().blueF();
81 }
82 }
83 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
84 if (fi.hasFaceWedgeTexCoords()) {
85 mat.hasTexture = true;
86 if constexpr (HasTexturePaths<MeshType>) {
87 mat.map_Kd = m.texturePath(f.textureIndex());
88 }
89 }
90 }
91 return mat;
92}
93
94template<EdgeConcept EdgeType, MeshConcept MeshType>
95ObjMaterial objMaterialFromEdge(const EdgeType& e, const MeshInfo& fi)
96{
97 ObjMaterial mat;
98 if constexpr (HasPerEdgeColor<MeshType>) {
99 if (fi.hasEdgeColors()) {
100 mat.hasColor = true;
101 mat.Kd.x() = e.color().redF();
102 mat.Kd.y() = e.color().greenF();
103 mat.Kd.z() = e.color().blueF();
104 }
105 }
106 return mat;
107}
108
109template<
110 ElementConcept ElementType,
111 MeshConcept MeshType,
112 LoggerConcept LogType = NullLogger>
113void writeElementObjMaterial(
114 const ElementType& e,
115 const MeshType& m,
116 const MeshInfo& fi,
117 ObjMaterial& lastMaterial,
118 std::map<ObjMaterial, std::string>& materialMap,
119 std::ostream& fp,
120 std::ostream& mtlfp,
121 const SaveSettings& settings,
122 LogType& log = nullLogger)
123{
124 ObjMaterial mat;
125 constexpr bool EL_IS_VERTEX = ElementType::ELEMENT_ID == ElemId::VERTEX;
126 constexpr bool EL_IS_FACE = ElementType::ELEMENT_ID == ElemId::FACE;
127 constexpr bool EL_IS_EDGE = ElementType::ELEMENT_ID == ElemId::EDGE;
128
129 if constexpr (EL_IS_VERTEX) {
130 mat = objMaterialFromVertex<typename MeshType::VertexType, MeshType>(
131 e, m, fi);
132 }
133 if constexpr (EL_IS_FACE) {
134 mat = objMaterialFromFace(e, m, fi);
135 }
136 if constexpr (EL_IS_EDGE) {
137 mat = objMaterialFromEdge<typename MeshType::EdgeType, MeshType>(e, fi);
138 }
139 if (!mat.isEmpty()) {
140 static const std::string MATERIAL_PREFIX = "MATERIAL_";
141 std::string mname; // name of the material of the vertex
142 auto it = materialMap.find(mat);
143 if (it == materialMap.end()) { // if it is a new material
144 // add the new material to the map
145 mname = MATERIAL_PREFIX + std::to_string(materialMap.size());
146 materialMap[mat] = mname;
147 // save the material in the mtl file
148 mtlfp << "newmtl " << mname << std::endl;
149 mtlfp << mat << std::endl;
150 if constexpr (HasTextureImages<MeshType>) {
151 if (settings.saveTextureImages && mat.hasTexture) {
152 // we need to save the texture image
153 // first, get the index of the texture: 0 if vertex,
154 // textureIndex if face
155 uint textureIndex = 0;
156 if constexpr (EL_IS_FACE) {
157 textureIndex = e.textureIndex();
158 }
159 const Texture& t = m.texture(textureIndex);
160 try {
161 t.image().save(m.meshBasePath() + mat.map_Kd);
162 }
163 catch (const std::runtime_error& e) {
164 log.log(e.what(), LogType::WARNING_LOG);
165 }
166 }
167 }
168 }
169 else { // get the name of the material
170 mname = it->second;
171 }
172 // if the material of the vertex is different from the last used, need
173 // to add usemtl
174 if (mat != lastMaterial) {
175 lastMaterial = mat;
176 fp << "usemtl " << mname << std::endl;
177 }
178 }
179}
180
181template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
182void saveObj(
183 const MeshType& m,
184 const std::string& filename,
185 std::ostream& fp,
186 std::ostream* mtlfp,
187 bool saveMtlFile,
188 LogType& log = nullLogger,
189 const SaveSettings& settings = SaveSettings())
190{
191 MeshInfo meshInfo(m);
192
193 // make sure that the given info contains only components that are actually
194 // available in the mesh. meshInfo will contain the intersection between the
195 // components that the user wants to save and the components that are
196 // available in the mesh.
197 if (!settings.info.isEmpty())
198 meshInfo = settings.info.intersect(meshInfo);
199
200 // if the mesh has both vertex and wedge texcords, will be saved just wedges
201 // because obj does not allow to save them both. In any case, also vertex
202 // texcoords will result saved as wedge texcoords in the final file.
203 if (meshInfo.hasVertexTexCoords() && meshInfo.hasFaceWedgeTexCoords()) {
204 meshInfo.setVertexTexCoords(false);
205 }
206
207 std::ofstream mtlftmp;
208 std::map<detail::ObjMaterial, std::string> materialMap;
209
210 bool useMtl =
211 meshInfo.hasVertexColors() || meshInfo.hasFaceColors() ||
212 (meshInfo.hasTextures() &&
213 (meshInfo.hasVertexTexCoords() || meshInfo.hasFaceWedgeTexCoords()));
214 if (useMtl) {
215 if (saveMtlFile) {
216 std::string mtlFileName =
217 FileInfo::fileNameWithExtension(filename) + ".mtl";
218
219 mtlftmp = openOutputFileStream(
220 FileInfo::pathWithoutFileName(filename) + mtlFileName);
221 mtlfp = &mtlftmp;
222
223 fp << "mtllib ./" << mtlFileName << std::endl;
224 }
225 else if (mtlfp == nullptr) {
226 useMtl = false;
227 }
228 }
229
230 detail::ObjMaterial lastMaterial;
231
232 // vertices
233 using VertexType = MeshType::VertexType;
234
235 fp << std::endl << "# Vertices" << std::endl;
236
237 for (const VertexType& v : m.vertices()) {
238 if (useMtl) { // mtl management
239 detail::writeElementObjMaterial<VertexType, MeshType>(
240 v,
241 m,
242 meshInfo,
243 lastMaterial,
244 materialMap,
245 fp,
246 *mtlfp,
247 settings,
248 log);
249 }
250 fp << "v ";
251 io::writeDouble(fp, v.coord().x(), false);
252 io::writeDouble(fp, v.coord().y(), false);
253 io::writeDouble(fp, v.coord().z(), false);
254 fp << std::endl;
255
256 if constexpr (HasPerVertexNormal<MeshType>) {
257 if (meshInfo.hasVertexNormals()) {
258 fp << "vn ";
259 io::writeDouble(fp, v.normal().x(), false);
260 io::writeDouble(fp, v.normal().y(), false);
261 io::writeDouble(fp, v.normal().z(), false);
262 fp << std::endl;
263 }
264 }
265 if constexpr (HasPerVertexTexCoord<MeshType>) {
266 if (meshInfo.hasVertexTexCoords()) {
267 fp << "vt ";
268 io::writeFloat(fp, v.texCoord().u(), false);
269 io::writeFloat(fp, v.texCoord().v(), false);
270 fp << std::endl;
271 }
272 }
273 }
274
275 // faces
276 if constexpr (HasFaces<MeshType>) {
277 using VertexType = MeshType::VertexType;
278 using FaceType = MeshType::FaceType;
279
280 fp << std::endl << "# Faces" << std::endl;
281
282 // indices of vertices that do not consider deleted vertices
283 std::vector<uint> vIndices = m.vertexCompactIndices();
284
285 uint wedgeTexCoord = 1;
286 for (const FaceType& f : m.faces()) {
287 if (useMtl) { // mtl management
288 detail::writeElementObjMaterial(
289 f,
290 m,
291 meshInfo,
292 lastMaterial,
293 materialMap,
294 fp,
295 *mtlfp,
296 settings,
297 log);
298 }
299 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
300 if (meshInfo.hasFaceWedgeTexCoords()) {
301 using WedgeTexCoordType = FaceType::WedgeTexCoordType;
302 for (const WedgeTexCoordType wt : f.wedgeTexCoords()) {
303 fp << "vt ";
304 io::writeFloat(fp, wt.u(), false);
305 io::writeFloat(fp, wt.v(), false);
306 fp << std::endl;
307 }
308 }
309 }
310
311 fp << "f ";
312 for (const VertexType* v : f.vertices()) {
313 fp << vIndices[m.index(v)] + 1;
314 if constexpr (HasPerVertexTexCoord<MeshType>) {
315 // we wrote texcoords along with vertices, each texcoord has
316 // the same index of its vertex
317 if (meshInfo.hasVertexTexCoords()) {
318 fp << "/" << vIndices[m.index(v)] + 1;
319 }
320 }
321 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
322 // we wrote texcoords before the face; indices are
323 // consecutive and wedge coords are the same of the number
324 // of vertices of the face
325 if (meshInfo.hasFaceWedgeTexCoords()) {
326 fp << "/" << wedgeTexCoord++;
327 }
328 }
329 fp << " ";
330 }
331 fp << std::endl;
332 }
333 }
334
335 if constexpr (HasEdges<MeshType>) {
336 using VertexType = MeshType::VertexType;
337 using EdgeType = MeshType::EdgeType;
338
339 fp << std::endl << "# Edges" << std::endl;
340
341 // indices of vertices that do not consider deleted vertices
342 std::vector<uint> vIndices = m.vertexCompactIndices();
343
344 for (const EdgeType& e : m.edges()) {
345 if (useMtl) { // mtl management
346 detail::writeElementObjMaterial(
347 e,
348 m,
349 meshInfo,
350 lastMaterial,
351 materialMap,
352 fp,
353 *mtlfp,
354 settings,
355 log);
356 }
357 fp << "l ";
358 fp << vIndices[m.index(e.vertex(0))] + 1 << " ";
359 fp << vIndices[m.index(e.vertex(1))] + 1 << std::endl;
360 }
361 }
362}
363
364} // namespace detail
365
366template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
367void saveObj(
368 const MeshType& m,
369 std::ostream& fp,
370 std::ostream& mtlfp,
371 const SaveSettings& settings,
372 LogType& log = nullLogger)
373{
374 detail::saveObj(m, "materials", fp, &mtlfp, false, log, settings);
375}
376
377template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
378void saveObj(
379 const MeshType& m,
380 std::ostream& fp,
381 std::ostream& mtlfp,
382 LogType& log = nullLogger,
383 const SaveSettings& settings = SaveSettings())
384{
385 detail::saveObj(m, "materials", fp, &mtlfp, false, log, settings);
386}
387
388template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
389void saveObj(
390 const MeshType& m,
391 std::ostream& fp,
392 const SaveSettings& settings,
393 LogType& log = nullLogger)
394{
395 detail::saveObj(m, "", fp, nullptr, false, log, settings);
396}
397
398template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
399void saveObj(
400 const MeshType& m,
401 std::ostream& fp,
402 LogType& log = nullLogger,
403 const SaveSettings& settings = SaveSettings())
404{
405 detail::saveObj(m, "", fp, nullptr, false, log, settings);
406}
407
408template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
409void saveObj(
410 const MeshType& m,
411 const std::string& filename,
412 const SaveSettings& settings,
413 LogType& log = nullLogger)
414{
415 std::ofstream fp = openOutputFileStream(filename, "obj");
416
417 detail::saveObj(m, filename, fp, nullptr, true, log, settings);
418}
419
420template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
421void saveObj(
422 const MeshType& m,
423 const std::string& filename,
424 LogType& log = nullLogger,
425 const SaveSettings& settings = SaveSettings())
426{
427 std::ofstream fp = openOutputFileStream(filename, "obj");
428
429 detail::saveObj(m, filename, fp, nullptr, true, log, settings);
430}
431
432} // namespace vcl
433
434#endif // VCL_LOAD_SAVE_OBJ_SAVE_H
static std::string fileNameWithExtension(const std::string &fullpath)
Get the filename with extension of a file.
Definition file_info.h:237
static std::string pathWithoutFileName(const std::string &fullpath)
Get the path of a file.
Definition file_info.h:197
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::FacesView faces
A view that allows to iterate overt the Face elements of an object.
Definition face.h:52
constexpr detail::EdgesView edges
A view that allows to iterate overt the Edge elements of an object.
Definition edge.h:52
constexpr detail::VerticesView vertices
A view that allows to iterate over the Vertex elements of an object.
Definition vertex.h:60