Easy3D 2.5.3
Tutorial_203_Viewer_wxWidgets

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_VIEWER_WXWIDGETS_VIEWER_H
28#define EASY3D_TUTORIAL_VIEWER_WXWIDGETS_VIEWER_H
29
30#include <easy3d/core/types.h>
31#include <wx/glcanvas.h>
32
33
34namespace easy3d {
35
36 class Model;
37 class Camera;
38 class Drawable;
39 class TrianglesDrawable;
40 class TextRenderer;
41
42 class Viewer : public wxGLCanvas {
43 public:
44 Viewer(wxWindow *parent,
45 const wxGLAttributes& glAttrs,
46 wxWindowID id = wxID_ANY,
47 const wxPoint &pos = wxDefaultPosition,
48 const wxSize &size = wxDefaultSize, long style = 0,
49 const wxString &title = "Easy3D-Viewer-wxWidgets");
50
51 ~Viewer() override;
52
71 virtual Model* add_model(const std::string& file_name, bool create_default_drawables);
72
90 virtual Model* add_model(Model* model, bool create_default_drawables);
91
98 bool delete_model(Model* model);
99
104 const std::vector<Model*>& models() const { return models_; }
105
114 Model* current_model() const;
115
121 bool save_current_model(const std::string& file_name) const;
122
133 bool add_drawable(Drawable* drawable);
134
140 bool delete_drawable(Drawable* drawable);
141
146 const std::vector<Drawable*>& drawables() const { return drawables_; }
147
151 void clear_scene();
152
158 void update() const;
159
166 void fit_screen(const easy3d::Model* model = nullptr);
167
172 float dpi_scaling() const;
173
178 void set_background_color(const easy3d::vec4& c) { background_color_ = c; }
179
184 const easy3d::vec4& background_color() const { return background_color_; }
185
187 Camera* camera() { return camera_; }
189 const Camera* camera() const { return camera_; }
190
197 bool snapshot(const std::string& image_file, bool bk_white = true) const;
198
199 protected:
200 void OnPaint(wxPaintEvent &event);
201 void OnSize(wxSizeEvent &event);
202 void OnMouse(wxMouseEvent &event);
203 void OnKeyDown(wxKeyEvent &event);
204
205 // rendering. Users can put their additional rendering function here by reimplementing it.
206 virtual void draw() const;
207
208 // This function will be called before the main draw procedure.
209 virtual void pre_draw();
210
211 // This function draws axes of the coordinate system, Easy3D logo, frame rate, etc. overlaid on the scene.
212 // It will be called after the main draw procedure.
213 virtual void post_draw();
214
215 void draw_corner_axes() const;
216
217 private:
218 void init();
219
220 private:
221 Camera* camera_;
222
223 wxGLContext *gl_context_;
224 bool initialized_;
225
226 vec4 background_color_;
227 TextRenderer* texter_;
228
229 TrianglesDrawable* drawable_axes_;
230
231 std::vector<Model*> models_;
232 int model_idx_;
233
234 // drawables independent of any model
235 std::vector<Drawable*> drawables_;
236
237 wxDECLARE_NO_COPY_CLASS(Viewer);
238 wxDECLARE_EVENT_TABLE();
239 };
240
241}
242
243#endif // EASY3D_TUTORIAL_VIEWER_WXWIDGETS_VIEWER_H
The base class of renderable 3D models.
Definition: model.h:49
virtual ~Viewer()
The destructor.
Definition: viewer.cpp:551
const easy3d::vec4 & background_color() const
Query the background color of the viewer.
Definition: viewer.h:174
void clear_scene()
Delete all visual contents of the viewer (all models and drawables).
Definition: viewer.cpp:556
const std::string & title() const
Query the window title of the viewer.
Definition: viewer.h:119
bool delete_drawable(Drawable *drawable)
Definition: viewer.cpp:1319
Model * current_model() const
Query the active model.
Definition: viewer.cpp:746
bool add_drawable(Drawable *drawable)
Add a drawable to the viewer to be visualized. After a drawable being added to the viewer,...
Definition: viewer.cpp:1302
Viewer(const std::string &title="Easy3D Viewer", int samples=4, int gl_major=3, int gl_minor=2, bool full_screen=false, bool resizable=true, int depth_bits=24, int stencil_bits=8, int width=800, int height=600)
Constructor.
Definition: viewer.cpp:74
bool delete_model(Model *model)
Delete a model. The memory of the model will be released and its existing drawables also be deleted.
Definition: viewer.cpp:1273
const std::vector< Model * > & models() const
Query the models managed by this viewer.
Definition: viewer.h:263
const std::vector< Drawable * > & drawables() const
Query the drawables managed by this viewer.
Definition: viewer.h:302
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
void fit_screen(const easy3d::Model *model=nullptr)
Moves the camera so that the entire scene or the active model is centered on the screen at a proper s...
Definition: viewer.cpp:1337
Camera * camera()
Returns the camera used by the viewer. See Camera.
Definition: viewer.h:177
void update() const
Update the display (i.e., repaint).
Definition: viewer.cpp:631
void set_background_color(const easy3d::vec4 &c)
Set the background color of the viewer.
Definition: viewer.h:168
bool snapshot() const
Take a snapshot of the screen and save it to a file.
Definition: viewer.cpp:1436
float dpi_scaling() const
Query the scaling factor for high DPI devices (e.g., MackBook pro).
Definition: viewer.h:162
Definition: collider.cpp:182
Vec< 4, float > vec4
A 4D point/vector of float type.
Definition: types.h:47

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 <wx/wxprec.h>
28#ifndef WX_PRECOMP
29#include <wx/wx.h>
30#endif
31
32#if !wxUSE_GLCANVAS
33#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
34#endif
35
36
37#include <easy3d/core/model.h>
38#include <easy3d/core/surface_mesh.h>
39#include <easy3d/core/graph.h>
40#include <easy3d/core/point_cloud.h>
41#include <easy3d/core/poly_mesh.h>
42#include <easy3d/renderer/opengl.h>
43#include <easy3d/renderer/opengl_util.h>
44#include <easy3d/renderer/opengl_error.h>
45#include <easy3d/renderer/renderer.h>
46#include <easy3d/renderer/manipulator.h>
47#include <easy3d/renderer/shader_program.h>
48#include <easy3d/renderer/shader_manager.h>
49#include <easy3d/renderer/transform.h>
50#include <easy3d/renderer/shape.h>
51#include <easy3d/renderer/camera.h>
52#include <easy3d/renderer/manipulated_camera_frame.h>
53#include <easy3d/renderer/drawable_points.h>
54#include <easy3d/renderer/drawable_lines.h>
55#include <easy3d/renderer/drawable_triangles.h>
56#include <easy3d/renderer/text_renderer.h>
57#include <easy3d/renderer/texture_manager.h>
58#include <easy3d/renderer/framebuffer_object.h>
59#include <easy3d/fileio/point_cloud_io.h>
60#include <easy3d/fileio/graph_io.h>
61#include <easy3d/fileio/surface_mesh_io.h>
62#include <easy3d/fileio/poly_mesh_io.h>
63#include <easy3d/fileio/ply_reader_writer.h>
64#include <easy3d/fileio/point_cloud_io_ptx.h>
65#include <easy3d/util/resource.h>
66#include <easy3d/util/file_system.h>
67#include <easy3d/util/setting.h>
68
69#include "viewer.h" // ensure "gl.h" is included after "glew.h"
70
71
72namespace easy3d {
73
74
75 wxBEGIN_EVENT_TABLE(Viewer, wxGLCanvas)
76 EVT_SIZE(Viewer::OnSize)
77 EVT_PAINT(Viewer::OnPaint)
78 EVT_MOUSE_EVENTS(Viewer::OnMouse)
79 EVT_KEY_DOWN(Viewer::OnKeyDown)
80 wxEND_EVENT_TABLE()
81
82
83 Viewer::Viewer(wxWindow *parent,
84 const wxGLAttributes& glAttrs,
85 wxWindowID id,
86 const wxPoint &pos,
87 const wxSize &size,
88 long style,
89 const wxString &title)
90 : wxGLCanvas(parent, glAttrs, id, pos, size, style | wxFULL_REPAINT_ON_RESIZE, title)
91 , initialized_(false)
92 , background_color_(setting::background_color)
93 , texter_(nullptr)
94 , drawable_axes_(nullptr)
95 , model_idx_(-1)
96 {
97 // Initialize logging (if it has not been initialized yet)
99 // initialize logging at the very beginning to make sure everything will be logged into the log file.
100 logging::initialize(false, true, true, false, "default", 9);
101
102 const int gl_major = 3;
103 const int gl_minor = 2;
104 VLOG(1) << "OpenGL version requested: " << gl_major << "." << gl_minor;
105 // Explicitly create a new rendering context instance for this canvas.
106 wxGLContextAttrs ctxAttrs;
107 ctxAttrs.PlatformDefaults().CoreProfile().OGLVersion(gl_major, gl_minor).EndList();
108 gl_context_ = new wxGLContext(this, nullptr, &ctxAttrs);
109
110 if (!gl_context_->IsOK()) {
111 LOG(ERROR) << "OpenGL version error. This app needs an OpenGL 3.2 capable driver.";
112 delete gl_context_;
113 gl_context_ = nullptr;
114 }
115
116 // create and set up the camera
117 camera_ = new Camera;
118 camera_->setType(Camera::PERSPECTIVE);
119 camera_->setUpVector(vec3(0, 0, 1)); // Z pointing up
120 camera_->setViewDirection(vec3(-1, 0, 0)); // X pointing out
121 camera_->showEntireScene();
122
123 easy3d::connect(&camera_->frame_modified, this, &Viewer::update);
124 }
125
126
128 delete camera_;
129 delete drawable_axes_;
130 delete texter_;
131
132 clear_scene();
133
134 ShaderManager::terminate();
136 delete gl_context_;
137
138 LOG(INFO) << "viewer terminated. Bye!";
139 }
140
141
142 void Viewer::init() {
143 // Load OpenGL and its extensions
144 if (OpenglUtil::init()) {
145 // Liangliang: a glew bug for Linux. Solution is here:
146 // https://github.com/wxWidgets/wxWidgets/issues/22710
147 glGetError(); // pull and ignore unhandled errors like GL_INVALID_ENUM
148 }
149
150#ifndef NDEBUG
151 opengl::setup_gl_debug_callback();
152#endif
153 VLOG(1) << "OpenGL vendor: " << glGetString(GL_VENDOR);
154 VLOG(1) << "OpenGL renderer: " << glGetString(GL_RENDERER);
155 VLOG(1) << "OpenGL version received: " << glGetString(GL_VERSION);
156 VLOG(1) << "GLSL version received: " << glGetString(GL_SHADING_LANGUAGE_VERSION);
157 VLOG(1) << "Number of samplers per pixel: " << OpenglUtil::samples();
158
159 glEnable(GL_DEPTH_TEST);
160 glDepthFunc(GL_LESS);
161
162 glDepthRange(0.0f, 1.0f);
163 glClearDepth(1.0f);
164 glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
165
166 // camera is manipulated by the mouse, working in the screen coordinate system
167 // (different from the viewport or framebuffer size, which are in pixel coordinates)
168 int w, h;
169 GetClientSize(&w, &h);
170 camera_->setScreenWidthAndHeight(w, h);
171 glViewport(0, 0, static_cast<int>(static_cast<float>(w) * dpi_scaling()), static_cast<int>(static_cast<float>(h) * dpi_scaling()));
172
173 // create TextRenderer renderer and load default fonts
174 texter_ = new TextRenderer(dpi_scaling());
175 texter_->add_font(resource::directory() + "/fonts/en_Earth-Normal.ttf");
176 texter_->add_font(resource::directory() + "/fonts/en_Roboto-Medium.ttf");
177
178#if 1 // Add a surface mesh of the bunny model
179 const std::vector<vec3> &points = resource::bunny_vertices;
180 const std::vector<unsigned int> &indices = resource::bunny_indices;
181 auto mesh = new SurfaceMesh;
182 mesh->set_name("bunny");
183 for (const auto& p : points)
184 mesh->add_vertex(p);
185 for (std::size_t i=0; i<indices.size(); i+=3)
186 mesh->add_triangle(SurfaceMesh::Vertex(static_cast<int>(indices[i])), SurfaceMesh::Vertex(static_cast<int>(indices[i+1])), SurfaceMesh::Vertex(static_cast<int>(indices[i+2])));
187 add_model(mesh, true);
188 fit_screen();
189 LOG(INFO) << "program initialized by creating a SurfaceMesh of the bunny model";
190#endif
191 }
192
193
194 void Viewer::clear_scene() {
195 for (auto m : models_) {
196 delete m->renderer();
197 delete m->manipulator();
198 delete m;
199 }
200 models_.clear();
201
202 for (auto d : drawables_)
203 delete d;
204 drawables_.clear();
205 }
206
207
208 void Viewer::update() const {
209 const_cast<Viewer*>(this)->Refresh();
210 }
211
212
213 void Viewer::OnPaint(wxPaintEvent & WXUNUSED(event)) {
214 // must always be here
215 wxPaintDC dc(this);
216
217 SetCurrent(*gl_context_);
218
219 // Initialize OpenGL
220 if (!initialized_) {
221 init();
222 initialized_ = true;
223 }
224
225 pre_draw();
226 draw();
227 post_draw();
228
229 // Flush
230 glFlush();
231
232 // Swap
233 SwapBuffers();
234 }
235
236
237 void Viewer::OnSize(wxSizeEvent & event) {
238 // Reset the OpenGL view aspect.
239 auto size = event.GetSize();
240 const int w = size.GetWidth();
241 const int h = size.GetHeight();
242 camera_->setScreenWidthAndHeight(w, h);
243 glViewport(0, 0, static_cast<int>(static_cast<float>(w) * dpi_scaling()), static_cast<int>(static_cast<float>(h) * dpi_scaling()));
244 }
245
246
247 void Viewer::OnMouse(wxMouseEvent &event) {
248 static bool left_down = false, right_down = false;
249 static int prev_x = event.GetX(), prev_y = event.GetY();
250
251 if (event.ButtonDown(wxMOUSE_BTN_ANY)) {
252 camera_->frame()->action_start();
253 if (event.LeftDown()) left_down = true;
254 else if (event.RightDown()) right_down = true;
255 }
256 else if (event.ButtonUp(wxMOUSE_BTN_ANY)) {
257 camera_->frame()->action_end();
258 if (event.LeftUp()) left_down = false;
259 else if (event.RightUp()) right_down = false;
260 }
261 else if (event.Dragging()) {
262 const int x = event.GetX();
263 const int y = event.GetY();
264 const int dx = x - prev_x;
265 const int dy = y - prev_y;
266 if (left_down)
267 camera_->frame()->action_rotate(x, y, dx, dy, camera_, ManipulatedFrame::NONE);
268 else if (right_down)
269 camera_->frame()->action_translate(x, y, dx, dy, camera_, ManipulatedFrame::NONE);
270 }
271 else {
272 const int rot = event.GetWheelRotation();
273 if (rot != 0)
274 camera_->frame()->action_zoom(rot > 0 ? 1 : -1, camera_);
275 }
276
277 prev_x = event.GetX();
278 prev_y = event.GetY();
279 }
280
281
282 void Viewer::OnKeyDown(wxKeyEvent &event) {
283 if (event.GetUnicodeKey() == wxKeyCode('A') && event.GetModifiers() == wxMOD_NONE) {
284 if (drawable_axes_)
285 drawable_axes_->set_visible(!drawable_axes_->is_visible());
286 }
287 else if (event.GetUnicodeKey() == wxKeyCode('C') && event.GetModifiers() == wxMOD_NONE) {
288 if (current_model())
290 } else if (event.GetUnicodeKey() == wxKeyCode('F') && event.GetModifiers() == wxMOD_NONE) {
291 fit_screen();
292 } else if (event.GetUnicodeKey() == wxKeyCode('M') && event.GetModifiers() == wxMOD_NONE) {
293 if (dynamic_cast<SurfaceMesh *>(current_model())) {
295 for (auto d: drawables)
296 d->set_smooth_shading(!d->smooth_shading());
297 }
298 } else if (event.GetUnicodeKey() == wxKeyCode('P') && event.GetModifiers() == wxMOD_NONE) {
299 if (camera_->type() == Camera::PERSPECTIVE)
300 camera_->setType(Camera::ORTHOGRAPHIC);
301 else
302 camera_->setType(Camera::PERSPECTIVE);
303 } else if (event.GetUnicodeKey() == WXK_SPACE && event.GetModifiers() == wxMOD_NONE) {
304 // Aligns camera
305 Frame frame;
306 frame.setTranslation(camera_->pivotPoint());
307 camera_->frame()->alignWithFrame(&frame, true);
308
309 // Aligns frame
310 //if (manipulatedFrame())
311 // manipulatedFrame()->alignWithFrame(camera_->frame());
312 }
313 else if (event.GetUnicodeKey() == wxKeyCode('[') && event.GetModifiers() == wxMOD_NONE) {
314 for (auto m: models_) {
315 for (auto d: m->renderer()->lines_drawables()) {
316 float size = d->line_width() - 1.0f;
317 if (size < 1)
318 size = 1;
319 d->set_line_width(size);
320 }
321 }
322 }
323 else if (event.GetUnicodeKey() == wxKeyCode(']') && event.GetModifiers() == wxMOD_NONE) {
324 for (auto m: models_) {
325 for (auto d: m->renderer()->lines_drawables()) {
326 float size = d->line_width() + 1.0f;
327 d->set_line_width(size);
328 }
329 }
330 }
331 else if (event.GetUnicodeKey() == wxKeyCode('-') && event.GetModifiers() == wxMOD_NONE) {
332 for (auto m: models_) {
333 for (auto d: m->renderer()->points_drawables()) {
334 float size = d->point_size() - 1.0f;
335 if (size < 1)
336 size = 1;
337 d->set_point_size(size);
338 }
339 }
340 }
341 else if (event.GetUnicodeKey() == wxKeyCode('=') && event.GetModifiers() == wxMOD_NONE) {
342 for (auto m: models_) {
343 for (auto d: m->renderer()->points_drawables()) {
344 float size = d->point_size() + 1.0f;
345 d->set_point_size(size);
346 }
347 }
348 }
349 else if (event.GetUnicodeKey() == wxKeyCode(',') && event.GetModifiers() == wxMOD_NONE) {
350 if (models_.empty())
351 model_idx_ = -1;
352 else
353 model_idx_ = int((model_idx_ - 1 + models_.size()) % models_.size());
354 if (model_idx_ >= 0) {
355 fit_screen(models_[model_idx_]);
356 std::cout << "current model: " << model_idx_ << ", " << models_[model_idx_]->name() << std::endl;
357 }
358 }
359 else if (event.GetUnicodeKey() == wxKeyCode('.') && event.GetModifiers() == wxMOD_NONE) {
360 if (models_.empty())
361 model_idx_ = -1;
362 else
363 model_idx_ = int((model_idx_ + 1) % models_.size());
364 if (model_idx_ >= 0) {
365 fit_screen(models_[model_idx_]);
366 std::cout << "current model: " << model_idx_ << ", " << models_[model_idx_]->name() << std::endl;
367 }
368 }
369 else if (event.GetUnicodeKey() == WXK_DELETE && event.GetModifiers() == wxMOD_NONE) {
370 if (current_model())
372 }
373 else if (event.GetUnicodeKey() == wxKeyCode('E') && event.GetModifiers() == wxMOD_NONE) {
374 if (current_model()) {
375 auto *edges = current_model()->renderer()->get_lines_drawable("edges");
376 if (edges)
377 edges->set_visible(!edges->is_visible());
378 }
379 }
380 else if (event.GetUnicodeKey() == wxKeyCode('V') && event.GetModifiers() == wxMOD_NONE) {
381 if (current_model()) {
382 auto vertices = current_model()->renderer()->get_points_drawable("vertices");
383 if (vertices)
384 vertices->set_visible(!vertices->is_visible());
385 }
386 }
387 else if (event.GetUnicodeKey() == wxKeyCode('B') && event.GetModifiers() == wxMOD_NONE) {
388 auto mesh = dynamic_cast<SurfaceMesh *>(current_model());
389 if (mesh) {
390 auto drawable = mesh->renderer()->get_lines_drawable("borders");
391 if (drawable)
392 drawable->set_visible(!drawable->is_visible());
393 }
394 }
395 else if (event.GetUnicodeKey() == wxKeyCode('L') && event.GetModifiers() == wxMOD_NONE) { // locked vertices
396 auto mesh = dynamic_cast<SurfaceMesh *>(current_model());
397 if (mesh) {
398 auto drawable = mesh->renderer()->get_points_drawable("locks");
399 if (drawable)
400 drawable->set_visible(!drawable->is_visible());
401 }
402 }
403 else if (event.GetUnicodeKey() == wxKeyCode('D') && event.GetModifiers() == wxMOD_NONE) {
404 if (current_model()) {
405 auto &output = std::cout;
406
407 output << "----------- " << file_system::simple_name(current_model()->name()) << " -----------\n";
408 if (dynamic_cast<SurfaceMesh *>(current_model())) {
409 auto model = dynamic_cast<SurfaceMesh *>(current_model());
410 output << "model is a surface mesh. #face: " << std::to_string(model->n_faces())
411 << ", #vertex: " + std::to_string(model->n_vertices())
412 << ", #edge: " + std::to_string(model->n_edges()) << std::endl;
413 } else if (dynamic_cast<PointCloud *>(current_model())) {
414 auto model = dynamic_cast<PointCloud *>(current_model());
415 output << "model is a point cloud. #vertex: " + std::to_string(model->n_vertices()) << std::endl;
416 } else if (dynamic_cast<Graph *>(current_model())) {
417 auto model = dynamic_cast<Graph *>(current_model());
418 output << "model is a graph. #vertex: " + std::to_string(model->n_vertices())
419 << ", #edge: " + std::to_string(model->n_edges()) << std::endl;
420 }
421
422 if (!current_model()->renderer()->points_drawables().empty()) {
423 output << "points drawables:\n";
424 for (auto d: current_model()->renderer()->points_drawables())
425 d->buffer_stats(output);
426 }
427 if (!current_model()->renderer()->lines_drawables().empty()) {
428 output << "lines drawables:\n";
429 for (auto d: current_model()->renderer()->lines_drawables())
430 d->buffer_stats(output);
431 }
432 if (!current_model()->renderer()->triangles_drawables().empty()) {
433 output << "triangles drawables:\n";
434 for (auto d: current_model()->renderer()->triangles_drawables())
435 d->buffer_stats(output);
436 }
437
438 current_model()->property_stats(output);
439 }
440 }
441
442 else if (event.GetUnicodeKey() == wxKeyCode('R') && event.GetModifiers() == wxMOD_NONE) {
443 // Reload the shader(s) - useful for writing/debugging shader code.
444 ShaderManager::reload();
445 }
446
447 update();
448 }
449
450
451 void Viewer::fit_screen(const easy3d::Model *model) {
452 if (!model && models_.empty() && drawables_.empty()) {
453 camera_->showEntireScene();
454 return;
455 }
456
457 auto visual_box = [](const Model *m) -> Box3 {
458 Box3 box = m->bounding_box();
459 for (auto d : m->renderer()->points_drawables()) box.grow(d->bounding_box());
460 for (auto d : m->renderer()->lines_drawables()) box.grow(d->bounding_box());
461 for (auto d : m->renderer()->triangles_drawables()) box.grow(d->bounding_box());
462 return box;
463 };
464
465 Box3 box;
466 if (model)
467 box = visual_box(model);
468 else {
469 for (auto m : models_)
470 box.grow(visual_box(m));
471 for (auto d : drawables_)
472 box.grow(d->bounding_box());
473 }
474
475 if (box.is_valid()) {
476 camera_->setSceneBoundingBox(box.min_point(), box.max_point());
477 camera_->showEntireScene();
478 update();
479 }
480 }
481
482
483 float Viewer::dpi_scaling() const {
484 return static_cast<float>(wxWindow::GetContentScaleFactor());
485 }
486
487
488 Model *Viewer::add_model(const std::string &file_path, bool create_default_drawables) {
489 const std::string file_name = file_system::convert_to_native_style(file_path);
490 for (auto m: models_) {
491 if (m->name() == file_name) {
492 LOG(WARNING) << "model has already been added to the viewer: " << file_name;
493 return m;
494 }
495 }
496
497 const std::string &ext = file_system::extension(file_name, true);
498 bool is_ply_mesh = false;
499 if (ext == "ply")
500 is_ply_mesh = (io::PlyReader::num_instances(file_name, "face") > 0);
501
502 Model *model = nullptr;
503 if ((ext == "ply" && is_ply_mesh) || ext == "obj" || ext == "off" || ext == "stl" || ext == "sm" ||
504 ext == "geojson" || ext == "trilist") { // mesh
505 model = SurfaceMeshIO::load(file_name);
506 } else if (ext == "ply" && io::PlyReader::num_instances(file_name, "edge") > 0) {
507 model = GraphIO::load(file_name);
508 } else if (ext == "plm" || ext == "pm" || ext == "mesh") {
509 model = PolyMeshIO::load(file_name);
510 } else { // point cloud
511 if (ext == "ptx") {
512 io::PointCloudIO_ptx serializer(file_name);
513 PointCloud *cloud = nullptr;
514 while ((cloud = serializer.load_next())) {
515 model = add_model(cloud, create_default_drawables);
516 Refresh();
517 }
518 return model; // returns the last cloud in the file.
519 } else
520 model = PointCloudIO::load(file_name);
521 }
522
523 if (model) {
524 model->set_name(file_name);
525 add_model(model, create_default_drawables);
526 }
527 return model;
528 }
529
530
531 Model *Viewer::add_model(Model *model, bool create) {
532 if (!model) {
533 LOG(WARNING) << "model is NULL.";
534 return nullptr;
535 }
536 for (auto m: models_) {
537 if (model == m) {
538 LOG(WARNING) << "model has already been added to the viewer: " << m->name();
539 return nullptr;
540 }
541 }
542
543 model->set_renderer(new Renderer(model, create));
544
545 int pre_idx = model_idx_;
546 models_.push_back(model);
547 model_idx_ = static_cast<int>(models_.size()) - 1; // make the last one current
548
549 if (model_idx_ != pre_idx) {
550 if (model_idx_ >= 0)
551 LOG(INFO) << "current model: " << model_idx_ << ", " << models_[model_idx_]->name();
552 }
553 return model;
554 }
555
556
557 bool Viewer::delete_model(Model *model) {
558 if (!model) {
559 LOG(WARNING) << "model is NULL.";
560 return false;
561 }
562
563 auto pos = std::find(models_.begin(), models_.end(), model);
564 if (pos != models_.end()) {
565 int pre_idx = model_idx_;
566 const std::string name = model->name();
567 models_.erase(pos);
568 delete model->renderer();
569 delete model->manipulator();
570 delete model;
571 model_idx_ = static_cast<int>(models_.size()) - 1; // make the last one current
572 LOG(INFO) << "model deleted: " << name;
573
574 if (model_idx_ != pre_idx) {
575 if (model_idx_ >= 0)
576 LOG(INFO) << "current model: " << model_idx_ << ", " << models_[model_idx_]->name();
577 }
578 return true;
579 } else {
580 LOG(WARNING) << "no such model: " << model->name();
581 return false;
582 }
583 }
584
585
586 Model *Viewer::current_model() const {
587 if (models_.empty())
588 return nullptr;
589 if (model_idx_ < models_.size())
590 return models_[model_idx_];
591 return nullptr;
592 }
593
594
595 bool Viewer::save_current_model(const std::string &file_name) const {
596 const Model *m = current_model();
597 if (!m) {
598 LOG(ERROR) << "no model exists";
599 return false;
600 }
601
602 bool saved = false;
603 if (dynamic_cast<const PointCloud *>(m))
604 saved = PointCloudIO::save(file_name, dynamic_cast<const PointCloud *>(m));
605 else if (dynamic_cast<const SurfaceMesh *>(m))
606 saved = SurfaceMeshIO::save(file_name, dynamic_cast<const SurfaceMesh *>(m));
607 else if (dynamic_cast<const Graph *>(m))
608 saved = GraphIO::save(file_name, dynamic_cast<const Graph *>(m));
609
610 if (saved)
611 LOG(INFO) << "file successfully saved";
612
613 return saved;
614 }
615
616
617 bool Viewer::snapshot(const std::string& file_name, bool bk_white) const {
618 SetCurrent(*gl_context_);
619
620 int x, y, w, h;
621 OpenglUtil::viewport(x, y, w, h);
622
623 FramebufferObject fbo(w, h, OpenglUtil::samples());
624 fbo.add_color_buffer();
625 fbo.add_depth_buffer();
626
627 fbo.bind();
628
629 if (bk_white)
630 glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
631 else
632 glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
633 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
634
635 const_cast<Viewer *>(this)->draw();
636
637 fbo.release();
638
639#if 1 // color render buffer
640 return fbo.snapshot_color(0, file_name);
641#else
642 // depth buffer
643 return fbo.snapshot_depth(file_name);
644#endif
645 }
646
647
648 bool Viewer::add_drawable(Drawable *drawable) {
649 if (!drawable) {
650 LOG(WARNING) << "drawable is NULL.";
651 return false;
652 }
653 for (auto d : drawables_) {
654 if (drawable == d) {
655 LOG(WARNING) << "drawable has already been added to the viewer.";
656 return false;
657 }
658 }
659
660 drawables_.push_back(drawable);
661 return true;
662 }
663
664
665 bool Viewer::delete_drawable(Drawable *drawable) {
666 if (!drawable) {
667 LOG(WARNING) << "drawable is NULL";
668 return false;
669 }
670
671 auto pos = std::find(drawables_.begin(), drawables_.end(), drawable);
672 if (pos != drawables_.end()) {
673 drawables_.erase(pos);
674 delete drawable;
675 return true;
676 } else {
677 LOG(WARNING) << "no such drawable: " << drawable->name();
678 return false;
679 }
680 }
681
682
683 void Viewer::draw_corner_axes() const {
684 ShaderProgram *program = ShaderManager::get_program("surface/surface");
685 if (!program) {
686 std::vector<ShaderProgram::Attribute> attributes = {
687 ShaderProgram::Attribute(ShaderProgram::POSITION, "vtx_position"),
688 ShaderProgram::Attribute(ShaderProgram::TEXCOORD, "vtx_texcoord"),
689 ShaderProgram::Attribute(ShaderProgram::COLOR, "vtx_color"),
690 ShaderProgram::Attribute(ShaderProgram::NORMAL, "vtx_normal")
691 };
692 program = ShaderManager::create_program_from_files("surface/surface", attributes);
693 }
694 if (!program)
695 return;
696
697 if (!drawable_axes_) {
698 float base = 0.5f; // the cylinder length, relative to the allowed region
699 float head = 0.2f; // the cone length, relative to the allowed region
700 std::vector<vec3> points, normals, colors;
701 shape::create_cylinder(0.03, 10, vec3(0, 0, 0), vec3(base, 0, 0), vec3(1, 0, 0), points, normals, colors);
702 shape::create_cylinder(0.03, 10, vec3(0, 0, 0), vec3(0, base, 0), vec3(0, 1, 0), points, normals, colors);
703 shape::create_cylinder(0.03, 10, vec3(0, 0, 0), vec3(0, 0, base), vec3(0, 0, 1), points, normals, colors);
704 shape::create_cone(0.06, 20, vec3(base, 0, 0), vec3(base + head, 0, 0), vec3(1, 0, 0), points, normals,
705 colors);
706 shape::create_cone(0.06, 20, vec3(0, base, 0), vec3(0, base + head, 0), vec3(0, 1, 0), points, normals,
707 colors);
708 shape::create_cone(0.06, 20, vec3(0, 0, base), vec3(0, 0, base + head), vec3(0, 0, 1), points, normals,
709 colors);
710 shape::create_sphere(vec3(0, 0, 0), 0.06, 20, 20, vec3(0, 1, 1), points, normals, colors);
711 const_cast<Viewer*>(this)->drawable_axes_ = new TrianglesDrawable("corner_axes");
712 drawable_axes_->update_vertex_buffer(points);
713 drawable_axes_->update_normal_buffer(normals);
714 drawable_axes_->update_color_buffer(colors);
715 drawable_axes_->set_property_coloring(State::VERTEX);
716 }
717 if (!drawable_axes_->is_visible())
718 return;
719
720 // The viewport is changed to fit the lower left corner.
721 int viewport[4];
722 glGetIntegerv(GL_VIEWPORT, viewport);
723
724 static int corner_frame_size = static_cast<int>(100 * dpi_scaling());
725 glViewport(0, 0, corner_frame_size, corner_frame_size);
726
727 // To make the axis appear over other objects: reserve a tiny bit of the
728 // front depth range. NOTE: do remember to restore it later.
729 glDepthRange(0, 0.01f);
730
731 const mat4 &proj = transform::ortho(-1, 1, -1, 1, -1, 1);
732 const mat4 &view = camera_->orientation().inverse().matrix();
733 const mat4 &MVP = proj * view;
734
735 // camera position is defined in world coordinate system.
736 const vec3 &wCamPos = camera_->position();
737 // it can also be computed as follows:
738 //const vec3& wCamPos = invMV * vec4(0, 0, 0, 1);
739 const mat4 &MV = camera_->modelViewMatrix();
740 const vec4 &wLightPos = inverse(MV) * setting::light_position;
741
742 program->bind();
743 program->set_uniform("MVP", MVP)
744 ->set_uniform("MANIP", mat4::identity())
745 ->set_uniform("NORMAL", mat3::identity()) // needs be padded when using uniform blocks
746 ->set_uniform("lighting", true)
747 ->set_uniform("two_sides_lighting", false)
748 ->set_uniform("smooth_shading", true)
749 ->set_uniform("wLightPos", wLightPos)
750 ->set_uniform("wCamPos", wCamPos)
751 ->set_uniform("ssaoEnabled", false)
752 ->set_uniform("per_vertex_color", true)
753 ->set_uniform("distinct_back_color", false)
754 ->set_block_uniform("Material", "ambient", setting::material_ambient)
755 ->set_block_uniform("Material", "specular", setting::material_specular)
756 ->set_block_uniform("Material", "shininess", &setting::material_shininess)
757 ->set_uniform("highlight", false)
758 ->set_uniform("clippingPlaneEnabled", false)
759 ->set_uniform("selected", false)
760 ->set_uniform("highlight_color", setting::highlight_color)
761 ->set_uniform("use_texture", false);
762 drawable_axes_->gl_draw();
763 program->release();
764
765 // restore the viewport
766 glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
767 glDepthRange(0.0f, 1.0f);
768 }
769
770
771 void Viewer::pre_draw() {
772 glClearColor(background_color_[0], background_color_[1], background_color_[2], 1.0f);
773 glClearDepth(1.0);
774 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
775 }
776
777
778 void Viewer::post_draw() {
779 // draw the Easy3D logo
780 if (texter_ && texter_->num_fonts() >=2) {
781 const float font_size = 15.0f;
782 const float offset = 20.0f * dpi_scaling();
783 texter_->draw("Easy3D", offset, offset, font_size, 0);
784 }
785
786 // ------- draw the axes indicating the orientation of the model ----------
787 draw_corner_axes();
788 }
789
790
791 void Viewer::draw() const {
792#if 0
793 // Example code: split the view to have different rendering styles for the same object/scene.
794 glEnable(GL_SCISSOR_TEST);
795 int viewport[4];
796 glGetIntegerv(GL_VIEWPORT, viewport);
797 glScissor(viewport[0], viewport[1], viewport[2] * 0.5f, viewport[3]);
798 for (const auto m : models_) {
799 for (auto d : m->renderer()->triangles_drawables())
800 d->draw(camera());
801 }
802 glScissor(viewport[2] * 0.5f, viewport[1], viewport[2] * 0.5f, viewport[3]);
803 for (const auto m : models_) {
804 for (auto d : m->renderer()->lines_drawables())
805 d->draw(camera());
806 }
807 glScissor(viewport[0], viewport[1], viewport[2], viewport[3]);
808
809#else
810
811 for (const auto m : models_) {
812 if (!m->renderer()->is_visible())
813 continue;
814
815 // Let's check if edges and surfaces are both shown. If true, we
816 // make the depth coordinates of the surface smaller, so that displaying
817 // the mesh and the surface together does not cause Z-fighting.
818 std::size_t count = 0;
819 for (auto d : m->renderer()->lines_drawables()) {
820 if (d->is_visible()) {
821 d->draw(camera_); easy3d_debug_log_gl_error
822 ++count;
823 }
824 }
825
826 for (auto d : m->renderer()->points_drawables()) {
827 if (d->is_visible())
828 d->draw(camera_); easy3d_debug_log_gl_error
829 }
830
831 if (count > 0) {
832 glEnable(GL_POLYGON_OFFSET_FILL);
833 glPolygonOffset(0.5f, -0.0001f);
834 }
835 for (auto d : m->renderer()->triangles_drawables()) {
836 if (d->is_visible())
837 d->draw(camera_); easy3d_debug_log_gl_error
838 }
839 if (count > 0)
840 glDisable(GL_POLYGON_OFFSET_FILL);
841 }
842
843 for (auto d : drawables_) {
844 if (d->is_visible())
845 d->draw(camera());
846 }
847#endif
848 }
849}
void showEntireScene() const
Definition: camera.cpp:709
quat orientation() const
Definition: camera.cpp:946
void setScreenWidthAndHeight(int width, int height)
Definition: camera.cpp:168
vec3 pivotPoint() const
Definition: camera.cpp:978
void setType(Type type)
Definition: camera.cpp:272
const mat4 & modelViewMatrix() const
Definition: camera.cpp:498
vec3 position() const
Definition: camera.cpp:905
Type type() const
Returns the Camera::Type of the Camera. Set by setType(). Mainly used by loadProjectionMatrix().
Definition: camera.h:267
ManipulatedCameraFrame * frame() const
Returns the ManipulatedFrame attached to the Camera.
Definition: camera.h:438
void setSceneBoundingBox(const vec3 &min, const vec3 &max)
Definition: camera.cpp:542
void update_vertex_buffer(const std::vector< vec3 > &vertices, bool dynamic=false)
Creates/Updates a single buffer.
Definition: drawable.cpp:142
void gl_draw() const
Definition: drawable.cpp:224
void alignWithFrame(const Frame *const frame, bool move=false, float threshold=0.0f)
Definition: frame.cpp:757
void grow(const Point &p)
Definition: box.h:216
static Graph * load(const std::string &file_name)
Reads a graph from file file_name.
Definition: graph_io.cpp:37
static bool save(const std::string &file_name, const Graph *graph)
Saves graph to file file_name.
Definition: graph_io.cpp:72
void action_rotate(int mouse_x, int mouse_y, int mouse_dx, int mouse_dy, Camera *camera, ScreenAxis axis) override
Definition: manipulated_camera_frame.cpp:71
virtual void action_end()
Definition: manipulated_frame.cpp:99
virtual void action_start()
Definition: manipulated_frame.cpp:95
static Mat< N, M, float > identity()
Static constructor return an N x M identity matrix. see also load_identity()
Definition: mat.h:483
void set_renderer(Renderer *r)
Sets the renderer of this model.
Definition: model.h:92
const std::string & name() const
The name of a model.
Definition: model.h:60
virtual void property_stats(std::ostream &output) const
Prints the names of all properties to an output stream (e.g., std::cout).
Definition: model.h:86
Renderer * renderer()
Gets the renderer of this model.
Definition: model.h:94
static void viewport(int &x, int &y, int &width, int &height)
Definition: opengl_util.cpp:222
static bool save(const std::string &file_name, const PointCloud *cloud)
Saves a point_cloud to a file.
Definition: point_cloud_io.cpp:83
static PointCloud * load(const std::string &file_name)
Reads a point cloud from file file_name.
Definition: point_cloud_io.cpp:37
static PolyMesh * load(const std::string &file_name)
Reads a polyhedral mesh from a file.
Definition: poly_mesh_io.cpp:37
Quat inverse() const
Inversion. Returns the inverse Quaternion (inverse rotation). Result has a negated axis() direction a...
Definition: quat.h:266
Mat4< FT > matrix() const
Returns the Quaternion associated 4x4 rotation matrix. Use glMultMatrixf(q.matrix()) to apply the rot...
Definition: quat.h:603
const std::vector< TrianglesDrawable * > & triangles_drawables() const
Definition: renderer.h:172
LinesDrawable * get_lines_drawable(const std::string &name) const
Definition: renderer.cpp:295
PointsDrawable * get_points_drawable(const std::string &name) const
Definition: renderer.cpp:286
void set_property_coloring(Location color_location, const std::string &color_name="")
Definition: state.cpp:97
static SurfaceMesh * load(const std::string &file_name)
Reads a surface mesh from a file.
Definition: surface_mesh_io.cpp:37
static bool save(const std::string &file_name, const SurfaceMesh *mesh)
Saves a surface mesh to a file.
Definition: surface_mesh_io.cpp:84
float draw(const std::string &text, float x, float y, float font_size, int font_id=0, const vec3 &font_color=vec3(0, 0, 0), bool upper_left=true) const
Definition: text_renderer.cpp:818
bool add_font(const std::string &font_file)
Definition: text_renderer.cpp:741
std::size_t num_fonts() const
Definition: text_renderer.h:76
static void terminate()
destroy all textures.
Definition: texture_manager.cpp:149
static std::size_t num_instances(const std::string &file_name, const std::string &element_name)
A quick check of the number of instances of a type of element. The typical use is to determine if a P...
std::string convert_to_native_style(const std::string &path)
Convert a path string such that it uses the current platform's path separators.
std::string simple_name(const std::string &path)
Gets file name without path but with extension (e.g, /a/b/c.Ext => c.Ext)
std::string extension(const std::string &path, bool lower=true)
Query the file extension without dot (e.g., /a/b/c.Ext => Ext).
void initialize(bool info_to_stdout=false, bool warning_to_stdcout=true, bool error_to_stdcout=true, bool verbose_to_stdcout=false, const std::string &log_file="default", int verbosity_threshold=9)
Initializes the logging module.
bool is_initialized()
Returns whether the logging has been initialized.
const std::vector< vec3 > bunny_vertices
Definition: resource.cpp:84
const std::vector< unsigned int > bunny_indices
Definition: resource.cpp:541
std::string directory()
Returns the resource directory (containing color maps, shaders, textures, fonts, etc....
Definition: resource.cpp:45
vec4 highlight_color
highlight: color for highlighted/selected primitives
Definition: setting.cpp:38
vec4 light_position
lighting
Definition: setting.cpp:40
vec4 background_color
background color of the viewer
Definition: setting.cpp:36
vec4 material_ambient
material
Definition: setting.cpp:43
void create_sphere(const vec3 &center, double radius, int slices, int stacks, const vec3 &color, std::vector< vec3 > &points, std::vector< vec3 > &normals, std::vector< vec3 > &colors)
Generates data (points, normals, and colors) for a 3D sphere.
Definition: shape.cpp:767
void create_cylinder(double radius, int slices, const vec3 &s, const vec3 &t, const vec3 &color, std::vector< vec3 > &points, std::vector< vec3 > &normals, std::vector< vec3 > &colors)
Prepares data (points, normals, and colors) for a 3D cylinder defined by two 3D points s and t.
Definition: shape.cpp:839
void create_cone(double radius, int slices, const vec3 &s, const vec3 &t, const vec3 &color, std::vector< vec3 > &points, std::vector< vec3 > &normals, std::vector< vec3 > &colors)
Prepares data (points, normals, and colors) for a 3D cone defined by two 3D points b and t....
Definition: shape.cpp:884
mat4 ortho(float left, float right, float bottom, float top, float zNear, float zFar)
Definition: transform.cpp:36
mat4 viewport(float w, float h)
Creates a viewport matrix. Simulating glViewport().
Definition: transform.cpp:128
Vec< 3, float > vec3
A 3D point/vector of float type.
Definition: types.h:45
GenericBox< 3, float > Box3
A 3D axis-aligned bounding box of float type.
Definition: types.h:109
int connect(SIGNAL *signal, FUNCTION const &slot)
Connects a function to the signal.
Definition: signal.h:202
Mat< N, N, T > inverse(const Mat< N, N, T > &m)
Return the inverse of N x N (square) matrix m.
Definition: mat.h:977
Mat4< float > mat4
A 4 by 4 matrix of float type.
Definition: types.h:68

The header file of the window 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_VIEWER_WXWIDGETS_WINDOW_H
28#define EASY3D_TUTORIAL_VIEWER_WXWIDGETS_WINDOW_H
29
30#include <wx/frame.h>
31
32
33namespace easy3d {
34
35 class Viewer;
36
37 class Window : public wxFrame {
38 public:
39 Window(wxFrame *parent, const wxString &title, const wxPoint &pos,
40 const wxSize &size, long style = wxDEFAULT_FRAME_STYLE);
41
42 Viewer *viewer() { return viewer_; }
43
44 private:
45 void menuFileOpen(wxCommandEvent &event);
46 void menuFileSave(wxCommandEvent &event);
47 void menuFileExit(wxCommandEvent &event);
48 void menuViewFitScreen(wxCommandEvent &event);
49 void menuViewSnapshot(wxCommandEvent &event);
50 void menuEditSubdivision(wxCommandEvent &event);
51 void menuHelpAbout(wxCommandEvent &event);
52
53 private:
54 Viewer *viewer_;
55
56 wxDECLARE_EVENT_TABLE();
57 };
58
59}
60
61#endif // EASY3D_TUTORIAL_VIEWER_WXWIDGETS_WINDOW_H

The source file of the window 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 <wx/wxprec.h>
28#ifndef WX_PRECOMP
29#include <wx/wx.h>
30#endif
31
32#include "window.h"
33#include "viewer.h"
34
35#include <easy3d/core/model.h>
36#include <easy3d/core/surface_mesh.h>
37#include <easy3d/algo/surface_mesh_subdivision.h>
38#include <easy3d/renderer/renderer.h>
39#include <easy3d/util/file_system.h>
40
41
42namespace easy3d {
43
44 wxDEFINE_EVENT(VIEW_FIT_SCREEN, wxCommandEvent);
45 wxDEFINE_EVENT(VIEW_SNAPSHOT, wxCommandEvent);
46 wxDEFINE_EVENT(EDIT_SUBDIVISION, wxCommandEvent);
47
48 wxBEGIN_EVENT_TABLE(Window, wxFrame)
49 EVT_MENU(wxID_OPEN, Window::menuFileOpen)
50 EVT_MENU(wxID_SAVE, Window::menuFileSave)
51 EVT_MENU(wxID_EXIT, Window::menuFileExit)
52 EVT_MENU(VIEW_FIT_SCREEN, Window::menuViewFitScreen)
53 EVT_MENU(VIEW_SNAPSHOT, Window::menuViewSnapshot)
54 EVT_MENU(EDIT_SUBDIVISION, Window::menuEditSubdivision)
55 EVT_MENU(wxID_HELP, Window::menuHelpAbout)
56 wxEND_EVENT_TABLE()
57
58 // Window constructor
59 Window::Window(wxFrame *parent, const wxString &title, const wxPoint &pos, const wxSize &size, long style)
60 : wxFrame(parent, wxID_ANY, title, pos, size, style) {
61#ifdef WIN32
62 SetIcon(wxICON(sample));
63#else
64 SetIcon(wxIcon(std::string(RESOURCE_DIR) + "/icons/sample.xpm"));
65#endif
66
67 // Make the "File" menu
68 auto fileMenu = new wxMenu;
69 fileMenu->Append(wxID_OPEN, "&Open...\tCTRL-O");
70 fileMenu->Append(wxID_SAVE, "&Save...\tCTRL-S");
71 fileMenu->AppendSeparator();
72 fileMenu->Append(wxID_EXIT, "E&xit\tALT-X");
73
74 // Make the "View" menu
75 auto viewMenu = new wxMenu;
76 viewMenu->Append(VIEW_FIT_SCREEN, "&Fit screen\tF");
77 viewMenu->Append(VIEW_SNAPSHOT, "&Snapshot...\tS");
78
79 // Make the "Edit" menu
80 auto editMenu = new wxMenu;
81 editMenu->Append(EDIT_SUBDIVISION, "&Subdivision");
82
83 // Make the "Help" menu
84 auto helpMenu = new wxMenu;
85 helpMenu->Append(wxID_HELP, "&About");
86
87 auto menuBar = new wxMenuBar;
88 menuBar->Append(fileMenu, "&File");
89 menuBar->Append(viewMenu, "&View");
90 menuBar->Append(editMenu, "&Edit");
91 menuBar->Append(helpMenu, "&Help");
92 SetMenuBar(menuBar);
93
94 wxGLAttributes glAttrib;
95 glAttrib.PlatformDefaults().RGBA().DoubleBuffer().Depth(24).Stencil(8).SampleBuffers(1).Samplers(4).EndList();
96 viewer_ = new Viewer(this, glAttrib, wxID_ANY, wxDefaultPosition, GetClientSize(), wxDEFAULT_FRAME_STYLE, title);
97
98 Show(true);
99 }
100
101 // File|Open... command
102 void Window::menuFileOpen(wxCommandEvent & WXUNUSED(event)) {
103 const std::string &title = "Choose a file";
104 wxString filename = wxFileSelector(title, "", "", "",
105 "Surface Mesh (*.ply;*.obj;*.off;*.stl;*.sm;*.geojson;*.trilist)|"
106 "*.ply;*.obj;*.off;*.stl;*.sm;*.geojson;*.trilist|"
107 "Point Cloud (*.ply;*.bin;*.ptx;*.las;*.laz;*.xyz;*.bxyz;*.vg;*.bvg;*.ptx)|"
108 "*.ply;*.bin;*.ptx;*.las;*.laz;*.xyz;*.bxyz;*.vg;*.bvg;*.ptx|"
109 "Polyhedral Mesh (*.plm;*.pm;*.mesh)|"
110 "*.plm;*.pm;*.mesh|"
111 "Graph (*.ply)|*.ply",
112 wxFD_OPEN);
113
114 if (!filename.IsEmpty()) {
115 auto model = viewer_->add_model(filename.ToStdString(), true);
116 if (model)
117 viewer_->fit_screen(model);
118 }
119 }
120
121 // File|Save... command
122 void Window::menuFileSave(wxCommandEvent & WXUNUSED(event)) {
123 const Model *m = viewer_->current_model();
124 if (!m) {
125 LOG(WARNING) << "no model exists";
126 return;
127 }
128
129 std::string name = m->name();
130 if (file_system::extension(name).empty()) // no extension?
131 name += ".ply"; // default to ply
132
133 const std::string &title = "Please specify a file name";
134 const std::string default_path = file_system::parent_directory(name);
135 const std::string simple_name = file_system::simple_name(name);
136 const std::string extension = file_system::extension(name);
137 wxString filename = wxFileSelector(title, default_path, simple_name, extension,
138 "Surface Mesh (*.ply;*.obj;*.off;*.stl;*.sm)|"
139 "*.ply;*.obj;*.off;*.stl;*.sm|"
140 "Point Cloud (*.ply;*.bin;*.las;*.laz;*.xyz;*.bxyz;*.vg;*.bvg)|"
141 "*.ply;*.bin;*.las;*.laz;*.xyz;*.bxyz;*.vg;*.bvg|"
142 "Polyhedral Mesh (*.plm;*.pm;*.mesh)|"
143 "*.plm;*.pm;*.mesh|"
144 "Graph (*.ply)|*.ply",
145 wxFD_SAVE);
146
147 if (!filename.IsEmpty())
148 viewer_->save_current_model(filename.ToStdString());
149 }
150
151 // View|Snapshot... command
152 void Window::menuViewSnapshot(wxCommandEvent &event) {
153 const std::string &title = "Please specify an image file name";
154 std::string name("untitled.png");
155
156 if (viewer_->current_model())
157 name = file_system::replace_extension(viewer_->current_model()->name(), "png");
158
159 const std::string default_path = file_system::parent_directory(name);
160 const std::string simple_name = file_system::simple_name(name);
161 const std::string extension = file_system::extension(name);
162 wxString filename = wxFileSelector(title, default_path, simple_name, extension,
163 "Image Files (*.png;*.jpg;*.bmp;*.ppm;*.tga)|"
164 "*.png;*.jpg;*.bmp;*.ppm;*.tga",
165 wxFD_SAVE);
166
167 if (!filename.IsEmpty())
168 viewer_->snapshot(filename.ToStdString(), true);
169 }
170
171 // File|Exit command
172 void Window::menuFileExit(wxCommandEvent & WXUNUSED(event)) {
173 // true is to force the frame to close
174 Close(true);
175 }
176
177 // View|FitScreen... command
178 void Window::menuViewFitScreen(wxCommandEvent &) {
179 viewer_->fit_screen();
180 }
181
182 // Edit|Subdivision... command
183 void Window::menuEditSubdivision(wxCommandEvent &) {
184 auto mesh = dynamic_cast<SurfaceMesh*>(viewer_->current_model());
185 if (!mesh) {
186 LOG(WARNING) << "current model is not a SurfaceMesh (or model does not exist)";
187 return;
188 }
189
191 mesh->renderer()->update();
192 viewer_->update();
193 }
194
195 // Help|About command
196 void Window::menuHelpAbout(wxCommandEvent & WXUNUSED(event)) {
197 wxMessageBox("Easy3D viewer based on wxWidgets");
198 }
199
200}
static bool loop(SurfaceMesh *mesh)
The Loop subdivision.
Definition: surface_mesh_subdivision.cpp:179
std::string parent_directory(const std::string &path)
Query the parent path from full name of a file or directory (e.g., /a/b/c.Ext => /a/b)
std::string replace_extension(std::string const &path, const std::string &ext)
Replaces the extension of the given file with 'ext'. If the file name does not have an extension,...

The header file of the application 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_VIEWER_WXWIDGETS_APPLICATION_H
28#define EASY3D_TUTORIAL_VIEWER_WXWIDGETS_APPLICATION_H
29
30#include <string>
31
32namespace easy3d {
33
34 class Application {
35 public:
36 explicit Application(const std::string &title = "Untitled", int width = 800, int height = 600);
37
38 int run(int argc, char **argv);
39 };
40
41}
42
43#endif // EASY3D_TUTORIAL_VIEWER_WXWIDGETS_APPLICATION_H

The source file of the application 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 <wx/wxprec.h>
28#ifndef WX_PRECOMP
29#include <wx/wx.h>
30#endif
31
32#include <wx/app.h>
33
34#include "application.h"
35#include "window.h"
36
37
38namespace easy3d {
39
40 class AppImpl : public wxApp {
41 public:
42 AppImpl(const std::string &title, int w, int h) : title_(title), width_(w), height_(h) {}
43
44 bool OnInit() wxOVERRIDE {
45 if (!wxApp::OnInit())
46 return false;
47 new Window(nullptr, title_, wxDefaultPosition, wxSize(width_, height_));
48 return true;
49 }
50 private:
51 std::string title_;
52 int width_;
53 int height_;
54 };
55
56
57 Application::Application(const std::string &title, int width, int height) {
58 wxDisableAsserts();
59 wxApp::SetInstance(new AppImpl(title, width, height));
60 }
61
62
63 int Application::run(int argc, char **argv) {
64 return wxEntry(argc, argv);
65 }
66
67}

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#if 1
28
29#include <easy3d/util/initializer.h>
30
31#include "application.h"
32
33
34using namespace easy3d;
35
36int main(int argc, char **argv) {
37 // initialize Easy3D.
38 initialize(true);
39
40 Application app(EXAMPLE_TITLE);
41 return app.run(argc, argv);
42}
43
44#else // The code below is for backup purpose.
45// It just works the same as the above code, but it is less structured and requires to expose the wxWidgets headers to client code.
46
47#include <wx/wxprec.h>
48#ifndef WX_PRECOMP
49#include <wx/wx.h>
50#endif
51
52#include <wx/app.h>
53#include "window.h"
54
55using namespace easy3d;
56
57class Easy3DApp : public wxApp {
58public:
59 virtual bool OnInit() wxOVERRIDE {
60 if (!wxApp::OnInit())
61 return false;
62 new Window(NULL, EXAMPLE_TITLE, wxDefaultPosition, wxSize(800, 600));
63 return true;
64 }
65};
66
67
68
69#if 1
70
71#ifdef WIN32_VIEWER_WITHOUT_CONSOLE
72int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wxCmdLineArgType lpCmdLine, int nCmdShow) {
73 wxDisableAsserts();
74 wxApp::SetInstance(new Easy3DApp);
75 return wxEntry(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
76}
77#else // WIN32_VIEWER_WITHOUT_CONSOLE
78int main(int argc, char **argv) {
79 wxDisableAsserts();
80 wxApp::SetInstance(new Easy3DApp);
81 return wxEntry(argc, argv);
82}
83#endif // WIN32_VIEWER_WITHOUT_CONSOLE
84
85#else
86
87#ifdef WIN32_VIEWER_WITHOUT_CONSOLE
88wxIMPLEMENT_APP(Easy3DApp);
89#else // WIN32_VIEWER_WITHOUT_CONSOLE
90wxIMPLEMENT_APP_CONSOLE(Easy3DApp);
91#endif // WIN32_VIEWER_WITHOUT_CONSOLE
92
93#endif
94
95#endif
void initialize(bool use_log_file, bool use_setting_file, const std::string &resource_dir)
Initialization of Easy3D.
Definition: initializer.cpp:35