eos 1.4.0
Loading...
Searching...
No Matches
read_obj.hpp
1/*
2 * eos - A 3D Morphable Model fitting library written in modern C++11/14.
3 *
4 * File: include/eos/core/read_obj.hpp
5 *
6 * Copyright 2017, 2018 Patrik Huber
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20#pragma once
21
22#ifndef EOS_READ_OBJ_HPP
23#define EOS_READ_OBJ_HPP
24
25#include "eos/core/Mesh.hpp"
26#include "eos/cpp17/optional.hpp"
27
28#include "Eigen/Core"
29
30#include <cassert>
31#include <fstream>
32#include <string>
33#include <tuple>
34#include <utility>
35#include <vector>
36
37namespace eos {
38namespace core {
39
40namespace detail {
41
47template <class ContainerType>
48void tokenize(const std::string& str, ContainerType& tokens, const std::string& delimiters = " ",
49 bool trim_empty = false)
50{
51 std::string::size_type pos, last_pos = 0;
52 const auto length = str.length();
53
54 using value_type = typename ContainerType::value_type;
55 using size_type = typename ContainerType::size_type;
56
57 while (last_pos < length + 1)
58 {
59 pos = str.find_first_of(delimiters, last_pos);
60 if (pos == std::string::npos)
61 {
62 pos = length;
63 }
64
65 if (pos != last_pos || !trim_empty)
66 tokens.push_back(value_type(str.data() + last_pos, (size_type)pos - last_pos));
67
68 last_pos = pos + 1;
69 }
70};
71
91inline std::pair<Eigen::Vector3f, cpp17::optional<Eigen::Vector3f>> parse_vertex(const std::string& line)
92{
93 std::vector<std::string> tokens;
94 tokenize(line, tokens, " ", true); // compress multiple (and leading/trailing) whitespaces
95 if (tokens.size() != 3 && tokens.size() != 6)
96 {
97 throw std::runtime_error(
98 "Encountered a vertex ('v') line that does not consist of either 3 ('x y z') "
99 "or 6 ('x y z r g b') numbers.");
100 }
101 const Eigen::Vector3f vertex(std::stof(tokens[0]), std::stof(tokens[1]), std::stof(tokens[2]));
102 cpp17::optional<Eigen::Vector3f> vertex_color;
103 if (tokens.size() == 6)
104 {
105 vertex_color = Eigen::Vector3f(std::stof(tokens[3]), std::stof(tokens[4]), std::stof(tokens[5]));
106 }
107 return {vertex, vertex_color};
108};
109
116inline Eigen::Vector2f parse_texcoords(const std::string& line)
117{
118 std::vector<std::string> tokens;
119 tokenize(line, tokens, " ");
120
121 if (tokens.size() != 2)
122 {
123 if (tokens.size() == 3 && std::stof(tokens[2]) == 0.f)
124 {
125 // we support this case too - 'u v 0'
126 } else
127 {
128 throw std::runtime_error(
129 "Encountered a texture coordinates ('vt') line that does not consist of two "
130 "('u v') numbers. 'u v w' is only supported in the special case if w is 0.");
131 }
132 }
133 const Eigen::Vector2f texcoords(std::stof(tokens[0]), std::stof(tokens[1]));
134 return texcoords;
135};
136
142inline void parse_vertex_normal(const std::string& line)
143{
144 throw std::runtime_error("Parsing \"vn\" is not yet implemented.");
145};
146
159inline auto parse_face(const std::string& line)
160{
161 using std::string;
162 using std::vector;
163
164 // Obj indices are 1-based.
165 vector<int> vertex_indices; // size() = 3 or 4
166 vector<int> texture_indices; // size() = 3 or 4
167 vector<int> normal_indices; // size() = 3 or 4
168
169 vector<string> tokens;
170 tokenize(line, tokens, " ", true); // compress multiple (and leading/trailing) whitespaces
171 if (tokens.size() != 3 && tokens.size() != 4)
172 {
173 // For now we need this to be 3 (triangles) or 4 (quads)
174 throw std::runtime_error("Encountered a faces ('f') line that does not consist of three or four "
175 "blocks of numbers. We currently only support 3 blocks (triangle meshes) "
176 "and 4 blocks (quad meshes).");
177 }
178 // Now for each of these tokens, we want to split on "/":
179 for (const auto& token : tokens)
180 {
181 vector<string> subtokens;
182 tokenize(token, subtokens, "/", false); // don't trim empty -if we do, we lose positional information.
183 assert(subtokens.size() > 0 && subtokens.size() <= 3);
184
185 // There should always be a vertex index:
186 vertex_indices.push_back(std::stoi(subtokens[0]) - 1); // obj indices are 1-based, so we subtract one.
187
188 if (subtokens.size() == 2)
189 {
190 // We've got texture coordinate indices as well:
191 texture_indices.push_back(std::stoi(subtokens[1]) - 1);
192 }
193
194 if (subtokens.size() == 3)
195 {
196 // We've got either texcoord indices *and* normal indices, or just normal indices:
197 if (!subtokens[1].empty())
198 {
199 // We do have texcoord indices
200 texture_indices.push_back(std::stoi(subtokens[1]) - 1);
201 }
202 // And push_back the normal indices:
203 normal_indices.push_back(std::stoi(subtokens[2]) - 1);
204 // Note if we use normal indices in the future: It looked like they can be zero in some cases?
205 }
206 }
207
208 return std::make_tuple(vertex_indices, texture_indices, normal_indices);
209};
210
211} /* namespace detail */
212
221inline Mesh read_obj(std::string filename)
222{
223 std::ifstream file(filename);
224 if (!file)
225 {
226 throw std::runtime_error(std::string("Could not open obj file: " + filename));
227 }
228
229 // We'll need these helper functions for the parsing:
230 const auto starts_with = [](const std::string& input, const std::string& match) {
231 return input.size() >= match.size() && std::equal(match.begin(), match.end(), input.begin());
232 };
233
234 /* auto trim_left = [](const std::string& input, std::string pattern = " \t") {
235 auto first = input.find_first_not_of(pattern);
236 if (first == std::string::npos)
237 {
238 return input;
239 }
240 return input.substr(first, input.size());
241 }; */
242
243 Mesh mesh;
244
245 std::string line;
246 while (getline(file, line))
247 {
248 if (starts_with(line, "#"))
249 {
250 continue;
251 }
252
253 if (starts_with(line, "v "))
254 { // matching with a space so that it doesn't match 'vt'
255 const auto vertex_data =
256 detail::parse_vertex(line.substr(2)); // pass the string without the first two characters
257 mesh.vertices.push_back(vertex_data.first);
258 if (vertex_data.second)
259 { // there are vertex colours:
260 mesh.colors.push_back(vertex_data.second.value());
261 }
262 }
263 if (starts_with(line, "vt "))
264 {
265 const auto texcoords = detail::parse_texcoords(line.substr(3));
266 mesh.texcoords.push_back(texcoords);
267 }
268 if (starts_with(line, "vn "))
269 {
270 // detail::parse_vertex_normal(line.substr(3));
271 // Not handled yet, our Mesh class doesn't contain normals right now anyway.
272 }
273 // There's other things like "vp ", which we don't handle
274 if (starts_with(line, "f "))
275 {
276 const auto face_data = detail::parse_face(line.substr(2));
277 if (std::get<0>(face_data).size() == 3) // 3 triangle indices, nothing to do:
278 {
279 mesh.tvi.push_back(
280 {std::get<0>(face_data)[0], std::get<0>(face_data)[1], std::get<0>(face_data)[2]});
281 }
282 // If their sizes are 4, we convert the quad to two triangles:
283 // Note: I think MeshLab does the same, it shows the number of "Faces" as twice the "f" entries
284 // in the obj.
285 else if (std::get<0>(face_data).size() == 4)
286 {
287 // Just create two faces with (quad[0], quad[1], quad[2]) and (quad[0], quad[2], quad[3]).
288 mesh.tvi.push_back(
289 {std::get<0>(face_data)[0], std::get<0>(face_data)[1], std::get<0>(face_data)[2]});
290 mesh.tvi.push_back(
291 {std::get<0>(face_data)[0], std::get<0>(face_data)[2], std::get<0>(face_data)[3]});
292 }
293 // We don't handle normal_indices for now.
294 }
295 // There can be other stuff in obj's like materials, named objects, etc., which are not handled here.
296 }
297 return mesh;
298}
299
300} /* namespace core */
301} /* namespace eos */
302
303#endif /* EOS_READ_OBJ_HPP */
Mesh read_obj(std::string filename)
Reads the given Wavefront .obj file into a Mesh.
Definition: read_obj.hpp:221
Namespace containing all of eos's 3D model fitting functionality.
This class represents a 3D mesh consisting of vertices, vertex colour information and texture coordin...
Definition: Mesh.hpp:45
std::vector< Eigen::Vector3f > colors
Colour information for each vertex. Expected to be in RGB order.
Definition: Mesh.hpp:47
std::vector< Eigen::Vector3f > vertices
3D vertex positions.
Definition: Mesh.hpp:46
std::vector< std::array< int, 3 > > tvi
Triangle vertex indices.
Definition: Mesh.hpp:50
std::vector< Eigen::Vector2f > texcoords
Texture coordinates.
Definition: Mesh.hpp:48