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
36namespace vcl {
37
38namespace detail {
39
40template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
41std::vector<std::string> saveObjMaterials(
42 const MeshType& m,
43 const std::string& meshBasePath,
44 std::ostream& mtlfp,
45 LogType& log = nullLogger)
46{
47 std::vector<std::string> materials;
48 if constexpr (HasMaterials<MeshType>) {
49 materials.reserve(m.materialsNumber());
50 for (uint i = 0; i < m.materialsNumber(); ++i) {
51 const Material& mat = m.material(i);
52 ObjMaterial omat(mat, i);
53 std::string matName = mat.name();
54 if (matName.empty()) {
55 matName = "MATERIAL_" + std::to_string(i);
56 omat.matName = matName;
57 }
58
59 mtlfp << "newmtl " << matName << std::endl;
60 mtlfp << omat << std::endl;
61 materials.push_back(matName);
62 }
63 }
64 return materials;
65}
66
67template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
68void saveObj(
69 const MeshType& m,
70 const std::string& filename,
71 std::ostream& fp,
72 std::ostream* mtlfp,
73 bool saveMtlFile,
74 const SaveSettings& settings = SaveSettings(),
75 LogType& log = nullLogger)
76{
77 MeshInfo meshInfo(m);
78
79 // base path for the mesh and material textures
80 std::string meshBasePath = FileInfo::pathWithoutFileName(filename);
81
82 // make sure that the given info contains only components that are actually
83 // available in the mesh. meshInfo will contain the intersection between the
84 // components that the user wants to save and the components that are
85 // available in the mesh.
86 if (!settings.info.isEmpty())
87 meshInfo = settings.info.intersect(meshInfo);
88
89 // if the mesh has both vertex and wedge texcords, will be saved just wedges
90 // because obj does not allow to save them both. In any case, also vertex
91 // texcoords will result saved as wedge texcoords in the final file.
92 if (meshInfo.hasPerVertexTexCoord() &&
93 meshInfo.hasPerFaceWedgeTexCoords()) {
94 meshInfo.setPerVertexTexCoord(false);
95 }
96
97 std::ofstream mtlftmp;
98
99 std::vector<std::string> materialNames;
100
101 bool useMtl = meshInfo.hasMaterials();
102 if (useMtl) {
103 if (saveMtlFile) {
104 std::string mtlFileName =
105 FileInfo::fileNameWithExtension(filename) + ".mtl";
106
107 mtlftmp = openOutputFileStream(
108 FileInfo::pathWithoutFileName(filename) + mtlFileName);
109 mtlfp = &mtlftmp;
110
111 fp << "mtllib ./" << mtlFileName << std::endl;
112 }
113 else if (mtlfp == nullptr) {
114 useMtl = false;
115 }
116 }
117
118 if (useMtl) {
119 materialNames = saveObjMaterials(m, meshBasePath, *mtlfp, log);
120 }
121
122 uint lastMaterial = UINT_NULL;
123
124 // vertices
125 using VertexType = MeshType::VertexType;
126
127 fp << std::endl << "# Vertices" << std::endl;
128
129 for (const VertexType& v : m.vertices()) {
130 fp << "v ";
131 io::writeDouble(fp, v.position().x(), false);
132 io::writeDouble(fp, v.position().y(), false);
133 io::writeDouble(fp, v.position().z(), false);
134 if constexpr (HasPerVertexColor<MeshType>) {
135 if (meshInfo.hasPerVertexColor()) {
136 io::writeFloat(fp, v.color().redF(), false);
137 io::writeFloat(fp, v.color().greenF(), false);
138 io::writeFloat(fp, v.color().blueF(), false);
139 }
140 }
141 fp << std::endl;
142
143 if constexpr (HasPerVertexNormal<MeshType>) {
144 if (meshInfo.hasPerVertexNormal()) {
145 fp << "vn ";
146 io::writeDouble(fp, v.normal().x(), false);
147 io::writeDouble(fp, v.normal().y(), false);
148 io::writeDouble(fp, v.normal().z(), false);
149 fp << std::endl;
150 }
151 }
152 if constexpr (HasPerVertexTexCoord<MeshType>) {
153 if (meshInfo.hasPerVertexTexCoord()) {
154 fp << "vt ";
155 io::writeFloat(fp, v.texCoord().u(), false);
156 io::writeFloat(fp, v.texCoord().v(), false);
157 fp << std::endl;
158 }
159 }
160 }
161
162 // faces
163 if constexpr (HasFaces<MeshType>) {
164 using VertexType = MeshType::VertexType;
165 using FaceType = MeshType::FaceType;
166
167 if (meshInfo.hasFaces()) {
168 fp << std::endl << "# Faces" << std::endl;
169
170 // indices of vertices that do not consider deleted vertices
171 std::vector<uint> vIndices = m.vertexCompactIndices();
172
173 uint wedgeTexCoord = 1;
174 for (const FaceType& f : m.faces()) {
175 if constexpr (HasMaterials<MeshType>) {
176 if (useMtl) { // mtl management
177 if (f.materialIndex() != lastMaterial) {
178 lastMaterial = f.materialIndex();
179 fp << "usemtl " << materialNames[lastMaterial]
180 << std::endl;
181 }
182 }
183 }
184 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
185 if (meshInfo.hasPerFaceWedgeTexCoords()) {
186 using WedgeTexCoordType = FaceType::WedgeTexCoordType;
187 for (const WedgeTexCoordType wt : f.wedgeTexCoords()) {
188 fp << "vt ";
189 io::writeFloat(fp, wt.u(), false);
190 io::writeFloat(fp, wt.v(), false);
191 fp << std::endl;
192 }
193 }
194 }
195
196 fp << "f ";
197 for (const VertexType* v : f.vertices()) {
198 fp << vIndices[m.index(v)] + 1;
199 if constexpr (HasPerVertexTexCoord<MeshType>) {
200 // we wrote texcoords along with vertices, each texcoord
201 // has the same index of its vertex
202 if (meshInfo.hasPerVertexTexCoord()) {
203 fp << "/" << vIndices[m.index(v)] + 1;
204 }
205 }
206 if constexpr (HasPerFaceWedgeTexCoords<MeshType>) {
207 // we wrote texcoords before the face; indices are
208 // consecutive and wedge coords are the same of the
209 // number of vertices of the face
210 if (meshInfo.hasPerFaceWedgeTexCoords()) {
211 fp << "/" << wedgeTexCoord++;
212 }
213 }
214 fp << " ";
215 }
216 fp << std::endl;
217 }
218 }
219 }
220
221 if constexpr (HasEdges<MeshType>) {
222 using VertexType = MeshType::VertexType;
223 using EdgeType = MeshType::EdgeType;
224
225 if (meshInfo.hasEdges()) {
226 fp << std::endl << "# Edges" << std::endl;
227
228 // indices of vertices that do not consider deleted vertices
229 std::vector<uint> vIndices = m.vertexCompactIndices();
230
231 for (const EdgeType& e : m.edges()) {
232 fp << "l ";
233 fp << vIndices[m.index(e.vertex(0))] + 1 << " ";
234 fp << vIndices[m.index(e.vertex(1))] + 1 << std::endl;
235 }
236 }
237 }
238
239 if constexpr (HasMaterials<MeshType>) {
240 if (useMtl && settings.saveTextureImages) {
241 using enum Material::TextureType;
242 saveTextureImages(m, meshBasePath, {BASE_COLOR, EMISSIVE}, log);
243 }
244 }
245}
246
247} // namespace detail
248
249template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
250void saveObj(
251 const MeshType& m,
252 std::ostream& fp,
253 std::ostream& mtlfp,
254 const SaveSettings& settings,
255 LogType& log = nullLogger)
256{
257 detail::saveObj(m, "materials", fp, &mtlfp, false, settings, log);
258}
259
260template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
261void saveObj(
262 const MeshType& m,
263 std::ostream& fp,
264 const SaveSettings& settings,
265 LogType& log = nullLogger)
266{
267 detail::saveObj(m, "", fp, nullptr, false, settings, log);
268}
269
270template<MeshConcept MeshType, LoggerConcept LogType = NullLogger>
271void saveObj(
272 const MeshType& m,
273 const std::string& filename,
274 const SaveSettings& settings,
275 LogType& log = nullLogger)
276{
277 std::ofstream fp = openOutputFileStream(filename, "obj");
278
279 detail::saveObj(m, filename, fp, nullptr, true, settings, log);
280}
281
282} // namespace vcl
283
284#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
TextureType
Defines the types of textures used in the PBR material model.
Definition material.h:62
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
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