Easy3D 2.5.3
Tutorial_308_TexturedMesh

The source file containing the main() function:

1/********************************************************************
2 * Copyright (C) 2015 Liangliang Nan <liangliang.nan@gmail.com>
3 * https://3d.bk.tudelft.nl/liangliang/
4 *
5 * This file is part of Easy3D. If it is useful in your research/work,
6 * I would be grateful if you show your appreciation by citing it:
7 * ------------------------------------------------------------------
8 * Liangliang Nan.
9 * Easy3D: a lightweight, easy-to-use, and efficient C++ library
10 * for processing and rendering 3D data.
11 * Journal of Open Source Software, 6(64), 3255, 2021.
12 * ------------------------------------------------------------------
13 *
14 * Easy3D is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License Version 3
16 * as published by the Free Software Foundation.
17 *
18 * Easy3D is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ********************************************************************/
26
27#include "viewer.h"
28#include <easy3d/util/resource.h>
29#include <easy3d/util/initializer.h>
30
31
32using namespace easy3d;
33
34
35// This example shows how to
36// - override the file loading function of the default easy3d viewer to visualize textured meshes;
37
38
39int main(int argc, char **argv) {
40 // initialize Easy3D.
41 initialize();
42
43 // create the viewer.
44 TexturedViewer viewer(EXAMPLE_TITLE);
45
46 //----------------------- Load a mesh from a file ------------------------
47
48 const std::string &file_name = resource::directory() + "/data/domik/domik.obj";
49 //const std::string& file_name = resource::directory() + "/data/cube/cube.obj";
50 if (!viewer.add_model(file_name)) {
51 LOG(ERROR) << "failed to load model. Please make sure the file exists and format is correct.";
52 return EXIT_FAILURE;
53 }
54
55 // run the viewer
56 return viewer.run();
57}
Definition: collider.cpp:182
void initialize(bool use_log_file, bool use_setting_file, const std::string &resource_dir)
Initialization of Easy3D.
Definition: initializer.cpp:35

The header file of the viewer class:

1/********************************************************************
2 * Copyright (C) 2015 Liangliang Nan <liangliang.nan@gmail.com>
3 * https://3d.bk.tudelft.nl/liangliang/
4 *
5 * This file is part of Easy3D. If it is useful in your research/work,
6 * I would be grateful if you show your appreciation by citing it:
7 * ------------------------------------------------------------------
8 * Liangliang Nan.
9 * Easy3D: a lightweight, easy-to-use, and efficient C++ library
10 * for processing and rendering 3D data.
11 * Journal of Open Source Software, 6(64), 3255, 2021.
12 * ------------------------------------------------------------------
13 *
14 * Easy3D is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License Version 3
16 * as published by the Free Software Foundation.
17 *
18 * Easy3D is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ********************************************************************/
26
27#ifndef EASY3D_TUTORIAL_TEXTURED_VIEWER_H
28#define EASY3D_TUTORIAL_TEXTURED_VIEWER_H
29
30#include <easy3d/viewer/viewer.h>
31
32
33namespace easy3d {
34
35
36 class SurfaceMesh;
37
38 // An enhanced viewer that can handle textured meshes (now it supports the OBJ format only).
39 class TexturedViewer : public Viewer
40 {
41 public:
42 explicit TexturedViewer(const std::string& title = "");
43
44 Model* add_model(const std::string& file_name, bool create_default_drawables = true) override;
45 };
46
47}
48
49#endif //EASY3D_TUTORIAL_TEXTURED_VIEWER_H

The source file of the viewer class:

1/********************************************************************
2 * Copyright (C) 2015 Liangliang Nan <liangliang.nan@gmail.com>
3 * https://3d.bk.tudelft.nl/liangliang/
4 *
5 * This file is part of Easy3D. If it is useful in your research/work,
6 * I would be grateful if you show your appreciation by citing it:
7 * ------------------------------------------------------------------
8 * Liangliang Nan.
9 * Easy3D: a lightweight, easy-to-use, and efficient C++ library
10 * for processing and rendering 3D data.
11 * Journal of Open Source Software, 6(64), 3255, 2021.
12 * ------------------------------------------------------------------
13 *
14 * Easy3D is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License Version 3
16 * as published by the Free Software Foundation.
17 *
18 * Easy3D is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ********************************************************************/
26
27#include "viewer.h"
28
29#include <unordered_map>
30
31#include <easy3d/core/surface_mesh.h>
32#include <easy3d/core/surface_mesh_builder.h>
33#include <easy3d/renderer/texture_manager.h>
34#include <easy3d/renderer/camera.h>
35#include <easy3d/renderer/drawable_triangles.h>
36#include <easy3d/renderer/renderer.h>
37#include <easy3d/algo/tessellator.h>
38#include <easy3d/util/file_system.h>
39#include <easy3d/util/logging.h>
40
41#define FAST_OBJ_IMPLEMENTATION
42#include <3rd_party/fastobj/fast_obj.h>
43
44
45namespace easy3d {
46
47
48 TexturedViewer::TexturedViewer(const std::string &title)
49 : Viewer(title) {
50 camera()->setUpVector(vec3(0, 1, 0));
51 }
52
53 namespace internal {
54
55 // each group is a set of faces (denoted by their indices) sharing the same material
56 struct Group : public std::vector<SurfaceMesh::Face> {
57 vec3 ambient;
58 vec3 diffuse;
59 vec3 specular;
60 float shininess;
61 std::string tex_file;
62 };
63 }
64
65
66 Model *TexturedViewer::add_model(const std::string &file_name, bool create_default_drawables) {
67 clear_scene(); // delete all existing models
68
69 if (!file_system::is_file(file_name)) {
70 LOG(ERROR) << "file does not exist: " << file_name;
71 return nullptr;
72 }
73
74 if (file_system::extension(file_name, true) != "obj")
75 return Viewer::add_model(file_name, create_default_drawables);
76
77 fastObjMesh *fom = fast_obj_read(file_name.c_str());
78 if (!fom) {
79 LOG(ERROR) << "failed reading file: " + file_name;
80 return nullptr;
81 }
82
83 // Attention: Valid indices in the fastObjMesh::indices array start from 1.
84 // A dummy position, normal and texture coordinate are added to the corresponding fastObjMesh arrays at
85 // element 0 and then an index of 0 is used to indicate that attribute is not present at the vertex.
86
87 // ------------------------ build the mesh ------------------------
88
89 // clear the mesh in case of existing data
90 auto mesh = new SurfaceMesh;
91 mesh->set_name(file_name);
92
93 SurfaceMeshBuilder builder(mesh);
94 builder.begin_surface();
95
96 // add vertices
97 // skip the first point
98 for (std::size_t v = 1; v < fom->position_count; ++v) {
99 // Should I create vertices later, to get rid of isolated vertices?
100 builder.add_vertex(vec3(fom->positions + v * 3));
101 }
102
103 // create texture coordinate property if texture coordinates present
104 SurfaceMesh::HalfedgeProperty<vec2> prop_texcoords;
105 if (fom->texcoord_count > 0 && fom->texcoords) // index starts from 1 and the first element is dummy
106 prop_texcoords = mesh->add_halfedge_property<vec2>("h:texcoord");
107
108 // create face color property if material information exists
109 SurfaceMesh::FaceProperty<vec3> prop_face_color;
110 if (fom->material_count > 0 && fom->materials) // index starts from 1 and the first element is dummy
111 prop_face_color = mesh->add_face_property<vec3>("f:color");
112
113 // find the face's halfedge that points to v.
114 auto find_face_halfedge = [](SurfaceMesh *mesh, SurfaceMesh::Face face,
115 SurfaceMesh::Vertex v) -> SurfaceMesh::Halfedge {
116 for (auto h : mesh->halfedges(face)) {
117 if (mesh->target(h) == v)
118 return h;
119 }
120 LOG_N_TIMES(3, ERROR) << "could not find a halfedge pointing to " << v << " in face " << face
121 << ". " << COUNTER;
122 return SurfaceMesh::Halfedge();
123 };
124
125 // group the faces according to the material
126 // each group is a set of faces sharing the same material
127 std::vector<internal::Group> groups(fom->material_count);
128
129 // for each shape
130 for (std::size_t ii = 0; ii < fom->group_count; ii++) {
131 const fastObjGroup &grp = fom->groups[ii];
132
133// if (grp.name)
134// std::cout << "group name: " << std::string(grp.name) << std::endl;
135
136 unsigned int idx = 0;
137 for (unsigned int jj = 0; jj < grp.face_count; ++jj) {
138 // number of vertices in the face
139 unsigned int fv = fom->face_vertices[grp.face_offset + jj];
140 std::vector<SurfaceMesh::Vertex> vertices;
141 std::vector<unsigned int> texcoord_ids;
142 for (unsigned int kk = 0; kk < fv; ++kk) { // for each vertex in the face
143 const fastObjIndex &mi = fom->indices[grp.index_offset + idx];
144 if (mi.p)
145 vertices.emplace_back(SurfaceMesh::Vertex(static_cast<int>(mi.p - 1)));
146 if (mi.t)
147 texcoord_ids.emplace_back(mi.t);
148 ++idx;
149 }
150
151 SurfaceMesh::Face face = builder.add_face(vertices);
152 if (face.is_valid()) {
153 // texture coordinates
154 if (prop_texcoords && texcoord_ids.size() == vertices.size()) {
155 auto begin = find_face_halfedge(mesh, face, builder.face_vertices()[0]);
156 auto cur = begin;
157 unsigned int vid = 0;
158 do {
159 unsigned int tid = texcoord_ids[vid++];
160 prop_texcoords[cur] = vec2(fom->texcoords + 2 * tid);
161 cur = mesh->next(cur);
162 } while (cur != begin);
163 }
164
165 // now materials
166 if (prop_face_color) {
167 unsigned int mat_id = fom->face_materials[grp.face_offset + jj];
168 const fastObjMaterial &mat = fom->materials[mat_id];
169 prop_face_color[face] = vec3(mat.Kd); // currently easy3d uses only diffuse
170 }
171
172 auto get_file_name = [](const char *name, const char* path) -> std::string {
173 std::string file_name;
174 if (name && file_system::is_file(std::string(name)))
175 file_name = std::string(name);
176 else if (path && file_system::is_file(std::string(path)))
177 file_name = std::string(path);
178 else if (name && path){
179 const std::string test_name = std::string(path) + "/" + std::string(name);
180 if (file_system::is_file(test_name))
181 file_name = test_name;
182 }
183 return file_name;
184 };
185
186 if (fom->material_count > 0 && fom->materials) {
187 unsigned int mat_id = fom->face_materials[grp.face_offset + jj];
188 const fastObjMaterial &mat = fom->materials[mat_id];
189 auto &g = groups[mat_id];
190 g.push_back(face);
191 g.ambient = vec3(mat.Ka);
192 g.diffuse = vec3(mat.Kd);
193 g.specular = vec3(mat.Ks);
194 g.shininess = static_cast<float>(mat.Ns);
195 g.tex_file = get_file_name(mat.map_Ka.name, mat.map_Ka.path); // use ambient texture it exists
196 if (g.tex_file.empty())
197 g.tex_file = get_file_name(mat.map_Kd.name, mat.map_Kd.path); // then try diffuse texture
198 if (g.tex_file.empty())
199 g.tex_file = get_file_name(mat.map_Ks.name, mat.map_Ks.path); // then try diffuse texture
200 }
201 }
202 }
203 }
204
205 builder.end_surface();
206
207 // since the mesh has been built, skip texture if material and texcoord information don't exist
208 if (fom->material_count == 0 || !fom->materials) {
209 Viewer::add_model(mesh, create_default_drawables);
210 return mesh;
211 }
212 else
213 Viewer::add_model(mesh, false);
214
215 mesh->update_vertex_normals();
216 auto normals = mesh->get_vertex_property<vec3>("v:normal");
217 auto points = mesh->get_vertex_property<vec3>("v:point");
218
219 Tessellator tessellator;
220 for (std::size_t i = 0; i < groups.size(); ++i) {
221 const auto &group = groups[i];
222 if (group.empty())
223 continue;
224
225 tessellator.reset();
226
227 for (auto face : group) {
228 tessellator.begin_polygon(mesh->compute_face_normal(face));
229 tessellator.set_winding_rule(Tessellator::WINDING_NONZERO); // or POSITIVE
230 tessellator.begin_contour();
231 for (auto h : mesh->halfedges(face)) {
232 auto v = mesh->target(h);
233 Tessellator::Vertex vtx(points[v], v.idx());
234 vtx.append(normals[v]);
235 if (prop_texcoords)
236 vtx.append(prop_texcoords[h]);
237 if (prop_face_color)
238 vtx.append(prop_face_color[face]);
239 tessellator.add_vertex(vtx);
240 }
241 tessellator.end_contour();
242 tessellator.end_polygon();
243 }
244
245 std::vector<vec3> d_points, d_colors, d_normals;
246 std::vector<vec2> d_texcoords;
247 const std::vector<Tessellator::Vertex*>& vts = tessellator.vertices();
248 for (auto v :vts) {
249 std::size_t offset = 0;
250 d_points.emplace_back(v->data() + offset);
251 offset += 3;
252 d_normals.emplace_back(v->data() + offset);
253 offset += 3;
254 if (prop_texcoords) {
255 d_texcoords.emplace_back(v->data() + offset);
256 offset += 2;
257 }
258 if (prop_face_color)
259 d_colors.emplace_back(v->data() + offset);
260 }
261
262 const auto &d_indices = tessellator.elements();
263
264 auto drawable = mesh->renderer()->add_triangles_drawable("faces_" + std::to_string(i));
265
266 drawable->update_element_buffer(d_indices);
267 drawable->update_vertex_buffer(d_points);
268 drawable->update_normal_buffer(d_normals);
269 if (!d_colors.empty())
270 drawable->update_color_buffer(d_colors);
271 if (!d_texcoords.empty())
272 drawable->update_texcoord_buffer(d_texcoords);
273
274 drawable->set_smooth_shading(false);
275 drawable->set_distinct_back_color(false); // ignore inconsistent orientations
276 if (prop_texcoords) {
277 if (!group.tex_file.empty()) {
278 Texture *tex = TextureManager::request(group.tex_file, Texture::REPEAT);
279 if (tex) {
280 drawable->set_texture_coloring(State::HALFEDGE, "h:texcoord", tex);
281 drawable->set_distinct_back_color(false);
282 LOG(INFO) << "texture created from " << group.tex_file;
283 }
284 }
285 }
286
287 if (!drawable->texture()) { // in case texture creation failed
288 if (prop_face_color)
289 drawable->set_property_coloring(State::Location::FACE, "f:color");
290 else
291 drawable->set_uniform_coloring(vec4(group.diffuse, 1.0f));
292 }
293 }
294
295 return mesh;
296 }
297
298}
static Texture * request(const std::string &image_file, Texture::WrapMode wrap=Texture::CLAMP_TO_EDGE, Texture::FilterMode filter=Texture::LINEAR)
Request a texture from the image file.
Definition: texture_manager.cpp:40
virtual Model * add_model(const std::string &file_name, bool create_default_drawables=true)
Add a model from a file to the viewer to be visualized. On success, the viewer will be in charge of t...
Definition: viewer.cpp:1204
bool is_file(const std::string &path)
Tests if 'path' is an existing file.
std::string extension(const std::string &path, bool lower=true)
Query the file extension without dot (e.g., /a/b/c.Ext => Ext).
Vec< 3, float > vec3
A 3D point/vector of float type.
Definition: types.h:45
Vec< 4, float > vec4
A 4D point/vector of float type.
Definition: types.h:47
Vec< 2, float > vec2
A 2D point/vector of float type.
Definition: types.h:43