Easy3D 2.6.1
Loading...
Searching...
No Matches
Tutorial_308_TexturedMesh/main.cpp

This example shows how to override the file loading function of the default easy3d viewer to visualize textured meshes.

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
Definition collider.cpp:182

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 // \cond
48
49 TexturedViewer::TexturedViewer(const std::string &title)
50 : Viewer(title) {
51 camera()->setUpVector(vec3(0, 1, 0));
52 }
53
54 namespace internal {
55
56 // each group is a set of faces (denoted by their indices) sharing the same material
57 struct Group : public std::vector<SurfaceMesh::Face> {
58 vec3 ambient;
59 vec3 diffuse;
60 vec3 specular;
61 float shininess;
62 std::string tex_file;
63 };
64 }
65
66
67 Model* TexturedViewer::add_model(const std::string &file_name, bool create_default_drawables) {
68 clear_scene(); // delete all existing models
69
70 if (!file_system::is_file(file_name)) {
71 LOG(ERROR) << "file does not exist: " << file_name;
72 return nullptr;
73 }
74
75 if (file_system::extension(file_name, true) != "obj")
76 return Viewer::add_model(file_name, create_default_drawables);
77
78 fastObjMesh *fom = fast_obj_read(file_name.c_str());
79 if (!fom) {
80 LOG(ERROR) << "failed reading file: " + file_name;
81 return nullptr;
82 }
83
84 // Attention: Valid indices in the fastObjMesh::indices array start from 1.
85 // A dummy position, normal and texture coordinate are added to the corresponding fastObjMesh arrays at
86 // element 0 and then an index of 0 is used to indicate that attribute is not present at the vertex.
87
88 // ------------------------ build the mesh ------------------------
89
90 // clear the mesh in case of existing data
91 auto mesh = new SurfaceMesh;
92 mesh->set_name(file_name);
93
94 SurfaceMeshBuilder builder(mesh);
95 builder.begin_surface();
96
97 // add vertices
98 // skip the first point
99 for (std::size_t v = 1; v < fom->position_count; ++v) {
100 // Should I create vertices later, to get rid of isolated vertices?
101 builder.add_vertex(vec3(fom->positions + v * 3));
102 }
103
104 // create texture coordinate property if texture coordinates present
105 SurfaceMesh::HalfedgeProperty<vec2> prop_texcoords;
106 if (fom->texcoord_count > 0 && fom->texcoords) // index starts from 1 and the first element is dummy
107 prop_texcoords = mesh->add_halfedge_property<vec2>("h:texcoord");
108
109 // create face color property if material information exists
110 SurfaceMesh::FaceProperty<vec3> prop_face_color;
111 if (fom->material_count > 0 && fom->materials) // index starts from 1 and the first element is dummy
112 prop_face_color = mesh->add_face_property<vec3>("f:color");
113
114 // find the face's halfedge that points to v.
115 auto find_face_halfedge = [](SurfaceMesh *mesh, SurfaceMesh::Face face,
116 SurfaceMesh::Vertex v) -> SurfaceMesh::Halfedge {
117 for (auto h : mesh->halfedges(face)) {
118 if (mesh->target(h) == v)
119 return h;
120 }
121 LOG_N_TIMES(3, ERROR) << "failed to find halfedge pointing to " << v << " in face " << face << ". " << 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 return Viewer::add_model(std::shared_ptr<SurfaceMesh>(mesh), create_default_drawables);
210 else
211 Viewer::add_model(std::shared_ptr<SurfaceMesh>(mesh), false);
212
213 mesh->update_vertex_normals();
214 auto normals = mesh->get_vertex_property<vec3>("v:normal");
215 auto points = mesh->get_vertex_property<vec3>("v:point");
216
217 Tessellator tessellator;
218 for (std::size_t i = 0; i < groups.size(); ++i) {
219 const auto &group = groups[i];
220 if (group.empty())
221 continue;
222
223 tessellator.reset();
224
225 for (auto face : group) {
226 tessellator.begin_polygon(mesh->compute_face_normal(face));
227 tessellator.set_winding_rule(Tessellator::WINDING_NONZERO); // or POSITIVE
228 tessellator.begin_contour();
229 for (auto h : mesh->halfedges(face)) {
230 auto v = mesh->target(h);
231 Tessellator::Vertex vtx(points[v], v.idx());
232 vtx.append(normals[v]);
233 if (prop_texcoords)
234 vtx.append(prop_texcoords[h]);
235 if (prop_face_color)
236 vtx.append(prop_face_color[face]);
237 tessellator.add_vertex(vtx);
238 }
239 tessellator.end_contour();
240 tessellator.end_polygon();
241 }
242
243 std::vector<vec3> d_points, d_colors, d_normals;
244 std::vector<vec2> d_texcoords;
245 const std::vector<Tessellator::Vertex*>& vts = tessellator.vertices();
246 for (auto v :vts) {
247 std::size_t offset = 0;
248 d_points.emplace_back(v->data() + offset);
249 offset += 3;
250 d_normals.emplace_back(v->data() + offset);
251 offset += 3;
252 if (prop_texcoords) {
253 d_texcoords.emplace_back(v->data() + offset);
254 offset += 2;
255 }
256 if (prop_face_color)
257 d_colors.emplace_back(v->data() + offset);
258 }
259
260 const auto &d_indices = tessellator.elements();
261
262 auto drawable = mesh->renderer()->add_triangles_drawable("faces_" + std::to_string(i));
263
264 drawable->update_element_buffer(d_indices);
265 drawable->update_vertex_buffer(d_points);
266 drawable->update_normal_buffer(d_normals);
267 if (!d_colors.empty())
268 drawable->update_color_buffer(d_colors);
269 if (!d_texcoords.empty())
270 drawable->update_texcoord_buffer(d_texcoords);
271
272 drawable->set_smooth_shading(false);
273 drawable->set_distinct_back_color(false); // ignore inconsistent orientations
274 if (prop_texcoords) {
275 if (!group.tex_file.empty()) {
276 Texture *tex = TextureManager::request(group.tex_file, Texture::REPEAT);
277 if (tex) {
278 drawable->set_texture_coloring(State::HALFEDGE, "h:texcoord", tex);
279 drawable->set_distinct_back_color(false);
280 LOG(INFO) << "texture created from " << group.tex_file;
281 }
282 }
283 }
284
285 if (!drawable->texture()) { // in case texture creation failed
286 if (prop_face_color)
287 drawable->set_property_coloring(State::Location::FACE, "f:color");
288 else
289 drawable->set_uniform_coloring(vec4(group.diffuse, 1.0f));
290 }
291 }
292
293 return mesh;
294 }
295
296 // \endcond
297
298}
@ FACE
Property defined on faces.
Definition state.h:70
@ HALFEDGE
Property defined on halfedges.
Definition state.h:72
@ REPEAT
Repeat the texture coordinate.
Definition texture.h:47
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:1242
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:44
Vec< 4, float > vec4
A 4D point/vector of float type.
Definition types.h:46
Vec< 2, float > vec2
A 2D point/vector of float type.
Definition types.h:42
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
42
43using namespace easy3d;
44
45
46int main(int argc, char **argv) {
47 // initialize Easy3D.
48 initialize();
49
50 // create the viewer.
51 TexturedViewer viewer(EXAMPLE_TITLE);
52
53 //----------------------- Load a mesh from a file ------------------------
54
55 const std::string &file_name = resource::directory() + "/data/domik/domik.obj";
56 //const std::string& file_name = resource::directory() + "/data/cube/cube.obj";
57 if (!viewer.add_model(file_name)) {
58 LOG(ERROR) << "failed to load model. Please make sure the file exists and format is correct.";
59 return EXIT_FAILURE;
60 }
61
62 // run the viewer
63 return viewer.run();
64}
std::string directory()
Returns the resource directory (containing color maps, shaders, textures, fonts, etc....
void initialize(bool info_to_stdout, bool use_log_file, bool use_setting_file, const std::string &resource_dir)
Initialization of Easy3D.
Definition initializer.cpp:39