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

This example shows how to create a simple viewer using 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 <memory>
31#include <wx/glcanvas.h>
32
33#include <easy3d/core/types.h>
34
35
36namespace easy3d {
37
38 class Model;
39 class Camera;
40 class Drawable;
41 class TrianglesDrawable;
42 class TextRenderer;
43
44 class Viewer : public wxGLCanvas {
45 public:
46 Viewer(wxWindow *parent,
47 const wxGLAttributes& glAttrs,
48 wxWindowID id = wxID_ANY,
49 const wxPoint &pos = wxDefaultPosition,
50 const wxSize &size = wxDefaultSize, long style = 0,
51 const wxString &title = "Easy3D-Viewer-wxWidgets");
52
53 ~Viewer() override;
54
73 virtual Model* add_model(const std::string& file_name, bool create_default_drawables = true);
74
92 Model* add_model(std::shared_ptr<Model> model, bool create_default_drawables = true);
93
100 bool delete_model(Model* model);
101
106 const std::vector< std::shared_ptr<Model> >& models() const { return models_; }
107
116 Model* current_model() const;
117
123 bool save_current_model(const std::string& file_name) const;
124
135 Drawable* add_drawable(std::shared_ptr<Drawable> drawable);
136
142 bool delete_drawable(Drawable* drawable);
143
148 const std::vector< std::shared_ptr<Drawable> >& drawables() const { return drawables_; }
149
153 void clear_scene();
154
160 void update() const;
161
168 void fit_screen(const easy3d::Model* model = nullptr);
169
174 float dpi_scaling() const;
175
180 void set_background_color(const easy3d::vec4& c) { background_color_ = c; }
181
186 const easy3d::vec4& background_color() const { return background_color_; }
187
189 Camera* camera() { return camera_.get(); }
191 const Camera* camera() const { return camera_.get(); }
192
199 bool snapshot(const std::string& image_file, bool bk_white = true) const;
200
201 protected:
202 void OnPaint(wxPaintEvent &event);
203 void OnSize(wxSizeEvent &event);
204 void OnMouse(wxMouseEvent &event);
205 void OnKeyDown(wxKeyEvent &event);
206
207 // rendering. Users can put their additional rendering function here by reimplementing it.
208 virtual void draw() const;
209
210 // This function will be called before the main draw procedure.
211 virtual void pre_draw();
212
213 // This function draws axes of the coordinate system, Easy3D logo, frame rate, etc. overlaid on the scene.
214 // It will be called after the main draw procedure.
215 virtual void post_draw();
216
217 void draw_corner_axes() const;
218
219 private:
220 void init();
221
222 private:
223 std::shared_ptr<Camera> camera_;
224
225 wxGLContext *gl_context_;
226 bool initialized_;
227
228 vec4 background_color_;
229 std::unique_ptr<TextRenderer> texter_;
230
231 std::unique_ptr<TrianglesDrawable> drawable_axes_;
232
233 std::vector< std::shared_ptr<Model> > models_;
234 int model_idx_;
235
236 // drawables independent of any model
237 std::vector< std::shared_ptr<Drawable> > drawables_;
238
239 wxDECLARE_NO_COPY_CLASS(Viewer);
240 wxDECLARE_EVENT_TABLE();
241 };
242
243}
244
245#endif // EASY3D_TUTORIAL_VIEWER_WXWIDGETS_VIEWER_H
The base class of renderable 3D models.
Definition model.h:50
virtual ~Viewer()
The destructor.
Definition viewer.cpp:553
void clear_scene()
Delete all visual contents of the viewer (all models and drawables).
Definition viewer.cpp:562
const std::string & title() const
Query the window title of the viewer.
Definition viewer.h:123
bool delete_drawable(Drawable *drawable)
Definition viewer.cpp:1368
Model * current_model() const
Query the active model.
Definition viewer.cpp:767
const vec4 & background_color() const
Query the background color of the viewer.
Definition viewer.h:178
const std::vector< std::shared_ptr< Drawable > > & drawables() const
Query the drawables managed by this viewer.
Definition viewer.h:337
const std::vector< std::shared_ptr< Model > > & models() const
Query the models managed by this viewer.
Definition viewer.h:287
void set_background_color(const vec4 &color)
Set the background color of the viewer.
Definition viewer.h:172
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:75
virtual 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:1318
void fit_screen(const 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:1387
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
Camera * camera()
Returns the camera used by the viewer. See Camera.
Definition viewer.h:181
void update() const
Update the display (i.e., repaint).
Definition viewer.cpp:630
Drawable * add_drawable(std::shared_ptr< Drawable > drawable)
Add a drawable to the viewer to be visualized. On success, the viewer will be in charge of the memory...
Definition viewer.cpp:1346
virtual bool snapshot() const
Take a snapshot of the screen and save it to a file.
Definition viewer.cpp:1487
float dpi_scaling() const
Query the scaling factor for high DPI devices (e.g., MackBook pro).
Definition viewer.h:166
Definition collider.cpp:182
Vec< 4, float > vec4
A 4D point/vector of float type.
Definition types.h:46

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

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 // \cond
45 wxDEFINE_EVENT(VIEW_FIT_SCREEN, wxCommandEvent);
46 wxDEFINE_EVENT(VIEW_SNAPSHOT, wxCommandEvent);
47 wxDEFINE_EVENT(EDIT_SUBDIVISION, wxCommandEvent);
48
49 wxBEGIN_EVENT_TABLE(Window, wxFrame)
50 EVT_MENU(wxID_OPEN, Window::menuFileOpen)
51 EVT_MENU(wxID_SAVE, Window::menuFileSave)
52 EVT_MENU(wxID_EXIT, Window::menuFileExit)
53 EVT_MENU(VIEW_FIT_SCREEN, Window::menuViewFitScreen)
54 EVT_MENU(VIEW_SNAPSHOT, Window::menuViewSnapshot)
55 EVT_MENU(EDIT_SUBDIVISION, Window::menuEditSubdivision)
56 EVT_MENU(wxID_HELP, Window::menuHelpAbout)
57 wxEND_EVENT_TABLE()
58
59 // Window constructor
60 Window::Window(wxFrame *parent, const wxString &title, const wxPoint &pos, const wxSize &size, long style)
61 : wxFrame(parent, wxID_ANY, title, pos, size, style) {
62#ifdef WIN32
63 SetIcon(wxICON(sample));
64#else
65 SetIcon(wxIcon(std::string(RESOURCE_DIR) + "/icons/sample.xpm"));
66#endif
67
68 // Make the "File" menu
69 auto fileMenu = new wxMenu;
70 fileMenu->Append(wxID_OPEN, "&Open...\tCTRL-O");
71 fileMenu->Append(wxID_SAVE, "&Save...\tCTRL-S");
72 fileMenu->AppendSeparator();
73 fileMenu->Append(wxID_EXIT, "E&xit\tALT-X");
74
75 // Make the "View" menu
76 auto viewMenu = new wxMenu;
77 viewMenu->Append(VIEW_FIT_SCREEN, "&Fit screen\tF");
78 viewMenu->Append(VIEW_SNAPSHOT, "&Snapshot...\tS");
79
80 // Make the "Edit" menu
81 auto editMenu = new wxMenu;
82 editMenu->Append(EDIT_SUBDIVISION, "&Subdivision");
83
84 // Make the "Help" menu
85 auto helpMenu = new wxMenu;
86 helpMenu->Append(wxID_HELP, "&About");
87
88 auto menuBar = new wxMenuBar;
89 menuBar->Append(fileMenu, "&File");
90 menuBar->Append(viewMenu, "&View");
91 menuBar->Append(editMenu, "&Edit");
92 menuBar->Append(helpMenu, "&Help");
93 SetMenuBar(menuBar);
94
95 wxGLAttributes glAttrib;
96 glAttrib.PlatformDefaults().RGBA().DoubleBuffer().Depth(24).Stencil(8).SampleBuffers(1).Samplers(4).EndList();
97 viewer_ = new Viewer(this, glAttrib, wxID_ANY, wxDefaultPosition, GetClientSize(), wxDEFAULT_FRAME_STYLE, title);
98
99 Show(true);
100 }
101
102 // File|Open... command
103 void Window::menuFileOpen(wxCommandEvent & WXUNUSED(event)) {
104 const std::string &title = "Choose a file";
105 wxString filename = wxFileSelector(title, "", "", "",
106 "Surface Mesh (*.ply;*.obj;*.off;*.stl;*.sm;*.geojson;*.trilist)|"
107 "*.ply;*.obj;*.off;*.stl;*.sm;*.geojson;*.trilist|"
108 "Point Cloud (*.ply;*.bin;*.ptx;*.las;*.laz;*.xyz;*.bxyz;*.vg;*.bvg;*.ptx)|"
109 "*.ply;*.bin;*.ptx;*.las;*.laz;*.xyz;*.bxyz;*.vg;*.bvg;*.ptx|"
110 "Polyhedral Mesh (*.plm;*.pm;*.mesh)|"
111 "*.plm;*.pm;*.mesh|"
112 "Graph (*.ply)|*.ply",
113 wxFD_OPEN);
114
115 if (!filename.IsEmpty()) {
116 auto model = viewer_->add_model(filename.ToStdString(), true);
117 if (model)
118 viewer_->fit_screen(model);
119 }
120 }
121
122 // File|Save... command
123 void Window::menuFileSave(wxCommandEvent & WXUNUSED(event)) {
124 const Model *m = viewer_->current_model();
125 if (!m) {
126 LOG(WARNING) << "no model exists";
127 return;
128 }
129
130 std::string name = m->name();
131 if (file_system::extension(name).empty()) // no extension?
132 name += ".ply"; // default to ply
133
134 const std::string &title = "Please specify a file name";
135 const std::string default_path = file_system::parent_directory(name);
136 const std::string simple_name = file_system::simple_name(name);
137 const std::string extension = file_system::extension(name);
138 wxString filename = wxFileSelector(title, default_path, simple_name, extension,
139 "Surface Mesh (*.ply;*.obj;*.off;*.stl;*.sm)|"
140 "*.ply;*.obj;*.off;*.stl;*.sm|"
141 "Point Cloud (*.ply;*.bin;*.las;*.laz;*.xyz;*.bxyz;*.vg;*.bvg)|"
142 "*.ply;*.bin;*.las;*.laz;*.xyz;*.bxyz;*.vg;*.bvg|"
143 "Polyhedral Mesh (*.plm;*.pm;*.mesh)|"
144 "*.plm;*.pm;*.mesh|"
145 "Graph (*.ply)|*.ply",
146 wxFD_SAVE);
147
148 if (!filename.IsEmpty())
149 viewer_->save_current_model(filename.ToStdString());
150 }
151
152 // View|Snapshot... command
153 void Window::menuViewSnapshot(wxCommandEvent &event) {
154 const std::string &title = "Please specify an image file name";
155 std::string name("untitled.png");
156
157 if (viewer_->current_model())
158 name = file_system::replace_extension(viewer_->current_model()->name(), "png");
159
160 const std::string default_path = file_system::parent_directory(name);
161 const std::string simple_name = file_system::simple_name(name);
162 const std::string extension = file_system::extension(name);
163 wxString filename = wxFileSelector(title, default_path, simple_name, extension,
164 "Image Files (*.png;*.jpg;*.bmp;*.ppm;*.tga)|"
165 "*.png;*.jpg;*.bmp;*.ppm;*.tga",
166 wxFD_SAVE);
167
168 if (!filename.IsEmpty())
169 viewer_->snapshot(filename.ToStdString(), true);
170 }
171
172 // File|Exit command
173 void Window::menuFileExit(wxCommandEvent & WXUNUSED(event)) {
174 // true is to force the frame to close
175 Close(true);
176 }
177
178 // View|FitScreen... command
179 void Window::menuViewFitScreen(wxCommandEvent &) {
180 viewer_->fit_screen();
181 }
182
183 // Edit|Subdivision... command
184 void Window::menuEditSubdivision(wxCommandEvent &) {
185 auto mesh = dynamic_cast<SurfaceMesh*>(viewer_->current_model());
186 if (!mesh) {
187 LOG(WARNING) << "current model is not a SurfaceMesh (or model does not exist)";
188 return;
189 }
190
192 mesh->renderer()->update();
193 viewer_->update();
194 }
195
196 // Help|About command
197 void Window::menuHelpAbout(wxCommandEvent & WXUNUSED(event)) {
198 wxMessageBox("Easy3D viewer based on wxWidgets");
199 }
200
201 // \endcond
202
203}
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
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 // \cond
41
42 class AppImpl : public wxApp {
43 public:
44 AppImpl(const std::string &title, int w, int h) : title_(title), width_(w), height_(h) {}
45
46 bool OnInit() wxOVERRIDE {
47 if (!wxApp::OnInit())
48 return false;
49 new Window(nullptr, title_, wxDefaultPosition, wxSize(width_, height_));
50 return true;
51 }
52 private:
53 std::string title_;
54 int width_;
55 int height_;
56 };
57
58
59 Application::Application(const std::string &title, int width, int height) {
60 wxDisableAsserts();
61 wxApp::SetInstance(new AppImpl(title, width, height));
62 }
63
64
65 int Application::run(int argc, char **argv) {
66 return wxEntry(argc, argv);
67 }
68
69 // \endcond
70
71}
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
45
46
47#if 1
48
49#include <easy3d/util/initializer.h>
50
51#include "application.h"
52
53
54using namespace easy3d;
55
56int main(int argc, char **argv) {
57 // initialize Easy3D.
58 initialize(false, true);
59
60 Application app(EXAMPLE_TITLE);
61 return app.run(argc, argv);
62}
63
64#else // The code below is for backup purpose.
65// It just works the same as the above code, but it is less structured and requires to expose the wxWidgets headers to client code.
66
67#include <wx/wxprec.h>
68#ifndef WX_PRECOMP
69#include <wx/wx.h>
70#endif
71
72#include <wx/app.h>
73#include "window.h"
74
75using namespace easy3d;
76
77class Easy3DApp : public wxApp {
78public:
79 virtual bool OnInit() wxOVERRIDE {
80 if (!wxApp::OnInit())
81 return false;
82 new Window(NULL, EXAMPLE_TITLE, wxDefaultPosition, wxSize(800, 600));
83 return true;
84 }
85};
86
87
88
89#if 1
90
91#ifdef WIN32_VIEWER_WITHOUT_CONSOLE
92int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wxCmdLineArgType lpCmdLine, int nCmdShow) {
93 wxDisableAsserts();
94 wxApp::SetInstance(new Easy3DApp);
95 return wxEntry(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
96}
97#else // WIN32_VIEWER_WITHOUT_CONSOLE
98int main(int argc, char **argv) {
99 wxDisableAsserts();
100 wxApp::SetInstance(new Easy3DApp);
101 return wxEntry(argc, argv);
102}
103#endif // WIN32_VIEWER_WITHOUT_CONSOLE
104
105#else
106
107#ifdef WIN32_VIEWER_WITHOUT_CONSOLE
108wxIMPLEMENT_APP(Easy3DApp);
109#else // WIN32_VIEWER_WITHOUT_CONSOLE
110wxIMPLEMENT_APP_CONSOLE(Easy3DApp);
111#endif // WIN32_VIEWER_WITHOUT_CONSOLE
112
113#endif
114
115#endif
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