MVE - Multi-View Environment mve-devel
Loading...
Searching...
No Matches
mesh_io_obj.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 2015, Simon Fuhrmann, Nils Moehrle
3 * TU Darmstadt - Graphics, Capture and Massively Parallel Computing
4 * All rights reserved.
5 *
6 * This software may be modified and distributed under the terms
7 * of the BSD 3-Clause license. See the LICENSE.txt file for details.
8 */
9
10#include <fstream>
11#include <iostream>
12#include <cerrno>
13#include <cstring>
14#include <map>
15
16#include "util/strings.h"
17#include "util/tokenizer.h"
18#include "util/exception.h"
19#include "util/file_system.h"
20#include "mve/mesh_io_obj.h"
21
24
25namespace
26{
27 struct ObjVertex
28 {
29 unsigned int vertex_id;
30 unsigned int texcoord_id;
31 unsigned int normal_id;
32
33 ObjVertex (void);
34 bool operator< (ObjVertex const & other) const;
35 };
36
37 inline
38 ObjVertex::ObjVertex (void)
39 : vertex_id(0)
40 , texcoord_id(0)
41 , normal_id(0)
42 {
43 }
44
45 inline bool
46 ObjVertex::operator< (ObjVertex const & other) const
47 {
48 return std::lexicographical_compare(&vertex_id, &normal_id + 1,
49 &other.vertex_id, &other.normal_id + 1);
50 }
51}
52
53void
54load_mtl_file (std::string const& filename,
55 std::map<std::string, std::string>* result)
56{
57 if (filename.empty())
58 throw std::invalid_argument("No filename given");
59
60 /* Open file. */
61 std::ifstream input(filename.c_str(), std::ios::binary);
62 if (!input.good())
63 throw util::FileException(filename, std::strerror(errno));
64
65 std::string material_name;
66 std::string buffer;
67 while (input.good())
68 {
69 std::getline(input, buffer);
72
73 if (buffer.empty())
74 continue;
75 if (input.eof())
76 break;
77
78 if (buffer[0] == '#')
79 {
80 std::cout << "MTL Loader: " << buffer << std::endl;
81 continue;
82 }
83
84 util::Tokenizer line;
85 line.split(buffer);
86
87 if (line[0] == "newmtl")
88 {
89 if (line.size() != 2)
90 throw util::Exception("Invalid material specification");
91
92 material_name = line[1];
93 }
94 else if (line[0] == "map_Kd")
95 {
96 if (line.size() != 2)
97 throw util::Exception("Invalid diffuse map specification");
98 if (material_name.empty())
99 throw util::Exception("Unbound material property");
100
101 std::string path = util::fs::join_path
102 (util::fs::dirname(filename), line[1]);
103 result->insert(std::make_pair(material_name, path));
104 material_name.clear();
105 }
106 else
107 {
108 std::cout << "MTL Loader: Skipping unimplemented material property "
109 << line[0] << std::endl;
110 }
111 }
112
113 /* Close the file stream. */
114 input.close();
115}
116
118load_obj_mesh (std::string const& filename)
119{
120 std::vector<ObjModelPart> obj_model_parts;
121 load_obj_mesh(filename, &obj_model_parts);
122
123 if (obj_model_parts.size() > 1)
124 throw util::Exception("OBJ file contains multiple parts");
125
126 return obj_model_parts[0].mesh;
127}
128
129void
130load_obj_mesh (std::string const& filename,
131 std::vector<ObjModelPart>* obj_model_parts)
132{
133 /* Precondition checks. */
134 if (filename.empty())
135 throw std::invalid_argument("No filename given");
136
137 /* Open file. */
138 std::ifstream input(filename.c_str(), std::ios::binary);
139 if (!input.good())
140 throw util::FileException(filename, std::strerror(errno));
141
142 std::vector<math::Vec3f> global_vertices;
143 std::vector<math::Vec3f> global_normals;
144 std::vector<math::Vec2f> global_texcoords;
145 std::map<std::string, std::string> materials;
146
148 mve::TriangleMesh::VertexList& vertices = mesh->get_vertices();
149 mve::TriangleMesh::NormalList& normals = mesh->get_vertex_normals();
150 mve::TriangleMesh::TexCoordList& texcoords = mesh->get_vertex_texcoords();
151 mve::TriangleMesh::FaceList& faces = mesh->get_faces();
152
153 typedef std::map<ObjVertex, unsigned int> VertexIndexMap;
154 VertexIndexMap vertex_map;
155 std::string material_name;
156 std::string new_material_name;
157 std::string buffer;
158 while (input.good())
159 {
160 std::getline(input, buffer);
163
164 if (input.eof() || !new_material_name.empty())
165 {
166 if (!texcoords.empty() && texcoords.size() != vertices.size())
167 throw util::Exception("Invalid number of texture coords");
168 if (!normals.empty() && normals.size() != vertices.size())
169 throw util::Exception("Invalid number of vertex normals");
170
171 if (!vertices.empty())
172 {
173 ObjModelPart obj_model_part;
174 obj_model_part.mesh = mve::TriangleMesh::create();
175 std::swap(vertices, obj_model_part.mesh->get_vertices());
176 std::swap(texcoords, obj_model_part.mesh->get_vertex_texcoords());
177 std::swap(normals, obj_model_part.mesh->get_vertex_normals());
178 std::swap(faces, obj_model_part.mesh->get_faces());
179 obj_model_part.texture_filename = materials[material_name];
180 obj_model_parts->push_back(obj_model_part);
181 }
182
183 mesh->clear();
184 vertex_map.clear();
185
186 material_name.swap(new_material_name);
187 new_material_name.clear();
188 }
189
190 if (buffer.empty())
191 continue;
192 if (input.eof())
193 break;
194
195 if (buffer[0] == '#')
196 {
197 /* Print all comments to STDOUT and forget data. */
198 std::cout << "OBJ Loader: " << buffer << std::endl;
199 continue;
200 }
201
202 util::Tokenizer line;
203 line.split(buffer);
204
205 if (line[0] == "v")
206 {
207 if (line.size() != 4 && line.size() != 5)
208 throw util::Exception("Invalid vertex coordinate specification");
209
210 math::Vec3f vertex(0.0f);
211 for (int i = 0; i < 3; ++i)
212 vertex[i] = util::string::convert<float>(line[1 + i]);
213
214 /* Convert homogeneous coordinates. */
215 if (line.size() == 5)
216 vertex /= util::string::convert<float>(line[4]);
217
218 global_vertices.push_back(vertex);
219 }
220 else if (line[0] == "vt")
221 {
222 if (line.size() != 3 && line.size() != 4)
223 throw util::Exception("Invalid texture coords specification");
224
225 math::Vec2f texcoord(0.0f);
226 for (int i = 0; i < 2; ++i)
227 texcoord[i] = util::string::convert<float>(line[1 + i]);
228
229 /* Convert homogeneous coordinates. */
230 if (line.size() == 4)
231 texcoord /= util::string::convert<float>(line[3]);
232
233 /* Invert y coordinate */
234 texcoord[1] = 1.0f - texcoord[1];
235
236 global_texcoords.push_back(texcoord);
237 }
238 else if (line[0] == "vn")
239 {
240 if (line.size() != 4 && line.size() != 5)
241 util::Exception("Invalid vertex normal specification");
242
243 math::Vec3f normal(0.0f);
244 for (int i = 0; i < 3; ++i)
245 normal[i] = util::string::convert<float>(line[1 + i]);
246
247 /* Convert homogeneous coordinates. */
248 if (line.size() == 5)
249 normal /= util::string::convert<float>(line[4]);
250
251 global_normals.push_back(normal);
252 }
253 else if (line[0] == "f")
254 {
255 if (line.size() != 4)
256 throw util::Exception("Only triangles supported");
257
258 for (int i = 0; i < 3; ++i)
259 {
260 util::Tokenizer tok;
261 tok.split(line[1 + i], '/');
262
263 if (tok.size() > 3)
264 throw util::Exception("Invalid face specification");
265
266 ObjVertex v;
267 v.vertex_id = util::string::convert<unsigned int>(tok[0]);
268 if (tok.size() >= 2 && !tok[1].empty())
269 v.texcoord_id = util::string::convert<unsigned int>(tok[1]);
270 if (tok.size() == 3 && !tok[2].empty())
271 v.normal_id = util::string::convert<unsigned int>(tok[2]);
272
273 if (v.vertex_id > global_vertices.size()
274 || v.texcoord_id > global_texcoords.size()
275 || v.normal_id > global_normals.size())
276 throw util::Exception("Invalid index in: " + buffer);
277
278 VertexIndexMap::const_iterator iter = vertex_map.find(v);
279 if (iter != vertex_map.end())
280 {
281 faces.push_back(iter->second);
282 }
283 else
284 {
285 vertices.push_back(global_vertices[v.vertex_id - 1]);
286 if (v.texcoord_id != 0)
287 texcoords.push_back(global_texcoords[v.texcoord_id - 1]);
288 if (v.normal_id != 0)
289 normals.push_back(global_normals[v.normal_id - 1]);
290 faces.push_back(vertices.size() - 1);
291 vertex_map[v] = vertices.size() - 1;
292 }
293 }
294 }
295 else if (line[0] == "usemtl")
296 {
297 if (line.size() != 2)
298 throw util::Exception("Invalid usemtl specification");
299 new_material_name = line[1];
300 }
301 else if (line[0] == "mtllib")
302 {
303 if (line.size() != 2)
304 throw util::Exception("Invalid material library specification");
305
306 std::string dir = util::fs::dirname(filename);
307 load_mtl_file(util::fs::join_path(dir, line[1]), &materials);
308 }
309 else
310 {
311 std::cout << "OBJ Loader: Skipping unsupported element: "
312 << line[0] << std::endl;
313 }
314 }
315
316 /* Close the file stream. */
317 input.close();
318}
319
320void
321save_obj_mesh (TriangleMesh::ConstPtr mesh, std::string const& filename)
322{
323 if (mesh == nullptr)
324 throw std::invalid_argument("Null mesh given");
325 if (filename.empty())
326 throw std::invalid_argument("No filename given");
327
328 mve::TriangleMesh::VertexList const& verts(mesh->get_vertices());
329 mve::TriangleMesh::FaceList const& faces(mesh->get_faces());
330
331 if (faces.size() % 3 != 0)
332 throw std::invalid_argument("Triangle indices not divisible by 3");
333
334 /* Open output file. */
335 std::ofstream out(filename.c_str(), std::ios::binary);
336 if (!out.good())
337 throw util::FileException(filename, std::strerror(errno));
338
339 out << "# Export generated by libmve\n";
340 for (std::size_t i = 0; i < verts.size(); ++i)
341 {
342 out << "v " << verts[i][0] << " " << verts[i][1]
343 << " " << verts[i][2] << "\n";
344 }
345
346 for (std::size_t i = 0; i < faces.size(); i += 3)
347 {
348 out << "f " << (faces[i + 0] + 1)
349 << " " << (faces[i + 1] + 1)
350 << " " << (faces[i + 2] + 1) << "\n";
351 }
352
353 /* Close output file. */
354 out.close();
355}
356
Vector class for arbitrary dimensions and types.
Definition vector.h:87
std::vector< math::Vec3f > VertexList
Definition mesh.h:33
std::vector< math::Vec3f > NormalList
Definition mesh.h:95
std::vector< math::Vec2f > TexCoordList
Definition mesh.h:96
std::shared_ptr< TriangleMesh > Ptr
Definition mesh.h:92
std::vector< VertexID > FaceList
Definition mesh.h:97
std::shared_ptr< TriangleMesh const > ConstPtr
Definition mesh.h:93
static Ptr create(void)
Definition mesh.h:295
Universal, simple exception class.
Definition exception.h:24
Exception class for file exceptions with additional filename.
Definition exception.h:53
Simple tokenizer.
Definition tokenizer.h:29
void split(std::string const &str, char delim=' ', bool keep_empty=false)
Very simple tokenziation at a given delimiter characater.
Definition tokenizer.h:62
unsigned int vertex_id
unsigned int normal_id
unsigned int texcoord_id
#define MVE_NAMESPACE_BEGIN
Definition defines.h:13
#define MVE_NAMESPACE_END
Definition defines.h:14
#define MVE_GEOM_NAMESPACE_END
Definition defines.h:20
#define MVE_GEOM_NAMESPACE_BEGIN
Definition defines.h:19
void save_obj_mesh(TriangleMesh::ConstPtr mesh, std::string const &filename)
Saves a triangle mesh to an OBJ model file.
mve::TriangleMesh::Ptr load_obj_mesh(std::string const &filename)
Loads a triangle mesh from an OBJ model file.
void load_mtl_file(std::string const &filename, std::map< std::string, std::string > *result)
void swap(mve::Image< T > &a, mve::Image< T > &b)
Specialization of std::swap for efficient image swapping.
Definition image.h:478
std::string dirname(std::string const &path)
Returns the directory name component of the given path.
std::string join_path(std::string const &path1, std::string const &path2)
Concatenate and canonicalize two paths.
void clip_newlines(std::string *str)
Clips newlines from the end of the string, in-place.
Definition strings.h:317
void clip_whitespaces(std::string *str)
Clips whitespaces from the front and end of the string, in-place.
Definition strings.h:299
mve::TriangleMesh::Ptr mesh
Definition mesh_io_obj.h:23
std::string texture_filename
Definition mesh_io_obj.h:24