Visual Computing Library  devel
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_IO_MESH_OBJ_SAVE_H
24#define VCL_IO_MESH_OBJ_SAVE_H
25
26#include "material.h"
27
28#include <vclib/io/file_info.h>
29#include <vclib/io/image/save.h>
30#include <vclib/io/mesh/settings.h>
31#include <vclib/io/write.h>
32
33#include <vclib/space/complex.h>
34#include <vclib/space/core.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.hasPerVertexColor()) {
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.hasPerVertexTexCoord()) {
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.hasPerFaceColor()) {
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.hasPerFaceWedgeTexCoords()) {
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.hasPerEdgeColor()) {
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 saveImage(t.image(), 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 const SaveSettings& settings = SaveSettings(),
189 LogType& log = nullLogger)
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.hasPerVertexTexCoord() &&
204 meshInfo.hasPerFaceWedgeTexCoords()) {
205 meshInfo.setPerVertexTexCoord(false);
206 }
207
208 std::ofstream mtlftmp;
209 std::map<detail::ObjMaterial, std::string> materialMap;
210
211 bool useMtl =
212 meshInfo.hasPerVertexColor() || meshInfo.hasPerFaceColor() ||
213 (meshInfo.hasTextures() && (meshInfo.hasPerVertexTexCoord() ||
214 meshInfo.hasPerFaceWedgeTexCoords()));
215 if (useMtl) {
216 if (saveMtlFile) {
217 std::string mtlFileName =
218 FileInfo::fileNameWithExtension(filename) + ".mtl";
219
220 mtlftmp = openOutputFileStream(
221 FileInfo::pathWithoutFileName(filename) + mtlFileName);
222 mtlfp = &mtlftmp;
223
224 fp << "mtllib ./" << mtlFileName << std::endl;
225 }
226 else if (mtlfp == nullptr) {
227 useMtl = false;
228 }
229 }
230
231 detail::ObjMaterial lastMaterial;
232
233 // vertices
234 using VertexType = MeshType::VertexType;
235
236 fp << std::endl << "# Vertices" << std::endl;
237
238 for (const VertexType& v : m.vertices()) {
239 if (useMtl) { // mtl management
240 detail::writeElementObjMaterial<VertexType, MeshType>(
241 v,
242 m,
243 meshInfo,
244 lastMaterial,
245 materialMap,
246 fp,
247 *mtlfp,
248 settings,
249 log);
250 }
251 fp << "v ";
252 io::writeDouble(fp, v.position().x(), false);
253 io::writeDouble(fp, v.position().y(), false);
254 io::writeDouble(fp, v.position().z(), false);
255 fp << std::endl;
256
257 if constexpr (HasPerVertexNormal<MeshType>) {
258 if (meshInfo.hasPerVertexNormal()) {
259 fp << "vn ";
260 io::writeDouble(fp, v.normal().x(), false);
261 io::writeDouble(fp, v.normal().y(), false);
262 io::writeDouble(fp, v.normal().z(), false);
263 fp << std::endl;
264 }
265 }
266 if constexpr (HasPerVertexTexCoord<MeshType>) {
267 if (meshInfo.hasPerVertexTexCoord()) {
268 fp << "vt ";
269 io::writeFloat(fp, v.texCoord().u(), false);
270 io::writeFloat(fp, v.texCoord().v(), false);
271 fp << std::endl;
272 }
273 }
274 }
275
276 // faces
277 if constexpr (HasFaces<MeshType>) {
278 using VertexType = MeshType::VertexType;
279 using FaceType = MeshType::FaceType;
280
281 if (meshInfo.hasFaces()) {
282 fp << std::endl << "# Faces" << std::endl;
283
284 // indices of vertices that do not consider deleted vertices
285 std::vector<uint> vIndices = m.vertexCompactIndices();
286
287 uint wedgeTexCoord = 1;
288 for (const FaceType& f : m.faces()) {
289 if (useMtl) { // mtl management
290 detail::writeElementObjMaterial(
291 f,
292 m,
293 meshInfo,
294 lastMaterial,
295 materialMap,
296 fp,
297 *mtlfp,
298 settings,
299 log);
300 }
301 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
302 if (meshInfo.hasPerFaceWedgeTexCoords()) {
303 using WedgeTexCoordType = FaceType::WedgeTexCoordType;
304 for (const WedgeTexCoordType wt : f.wedgeTexCoords()) {
305 fp << "vt ";
306 io::writeFloat(fp, wt.u(), false);
307 io::writeFloat(fp, wt.v(), false);
308 fp << std::endl;
309 }
310 }
311 }
312
313 fp << "f ";
314 for (const VertexType* v : f.vertices()) {
315 fp << vIndices[m.index(v)] + 1;
316 if constexpr (HasPerVertexTexCoord<MeshType>) {
317 // we wrote texcoords along with vertices, each texcoord
318 // has the same index of its vertex
319 if (meshInfo.hasPerVertexTexCoord()) {
320 fp << "/" << vIndices[m.index(v)] + 1;
321 }
322 }
323 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
324 // we wrote texcoords before the face; indices are
325 // consecutive and wedge coords are the same of the
326 // number of vertices of the face
327 if (meshInfo.hasPerFaceWedgeTexCoords()) {
328 fp << "/" << wedgeTexCoord++;
329 }
330 }
331 fp << " ";
332 }
333 fp << std::endl;
334 }
335 }
336 }
337
338 if constexpr (HasEdges<MeshType>) {
339 using VertexType = MeshType::VertexType;
340 using EdgeType = MeshType::EdgeType;
341
342 if (meshInfo.hasEdges()) {
343 fp << std::endl << "# Edges" << std::endl;
344
345 // indices of vertices that do not consider deleted vertices
346 std::vector<uint> vIndices = m.vertexCompactIndices();
347
348 for (const EdgeType& e : m.edges()) {
349 if (useMtl) { // mtl management
350 detail::writeElementObjMaterial(
351 e,
352 m,
353 meshInfo,
354 lastMaterial,
355 materialMap,
356 fp,
357 *mtlfp,
358 settings,
359 log);
360 }
361 fp << "l ";
362 fp << vIndices[m.index(e.vertex(0))] + 1 << " ";
363 fp << vIndices[m.index(e.vertex(1))] + 1 << std::endl;
364 }
365 }
366 }
367}
368
369} // namespace detail
370
371template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
372void saveObj(
373 const MeshType& m,
374 std::ostream& fp,
375 std::ostream& mtlfp,
376 const SaveSettings& settings,
377 LogType& log = nullLogger)
378{
379 detail::saveObj(m, "materials", fp, &mtlfp, false, settings, log);
380}
381
382template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
383void saveObj(
384 const MeshType& m,
385 std::ostream& fp,
386 const SaveSettings& settings,
387 LogType& log = nullLogger)
388{
389 detail::saveObj(m, "", fp, nullptr, false, settings, log);
390}
391
392template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
393void saveObj(
394 const MeshType& m,
395 const std::string& filename,
396 const SaveSettings& settings,
397 LogType& log = nullLogger)
398{
399 std::ofstream fp = openOutputFileStream(filename, "obj");
400
401 detail::saveObj(m, filename, fp, nullptr, true, settings, log);
402}
403
404} // namespace vcl
405
406#endif // VCL_IO_MESH_OBJ_SAVE_H
static std::string fileNameWithExtension(const std::string &fullpath)
Get the filename with extension of a file.
Definition file_info.h:240
static std::string pathWithoutFileName(const std::string &fullpath)
Get the path of a file.
Definition file_info.h:200
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 detail::FacesView faces
A view that allows to iterate overt the Face elements of an object.
Definition face.h:84
constexpr detail::EdgesView edges
A view that allows to iterate overt the Edge elements of an object.
Definition edge.h:84
constexpr detail::VerticesView vertices
A view that allows to iterate over the Vertex elements of an object.
Definition vertex.h:92