This example shows how to create a simple viewer using wxWidgets.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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"
69
70
72
73
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
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)
92 , model_idx_(-1)
93 {
94
96
98
99 const int gl_major = 3;
100 const int gl_minor = 2;
101 VLOG(1) << "OpenGL version requested: " << gl_major << "." << gl_minor;
102
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
114 camera_ = std::make_shared<Camera>();
116 camera_->setUpVector(
vec3(0, 0, 1));
117 camera_->setViewDirection(
vec3(-1, 0, 0));
118 camera_->showEntireScene();
119
121 }
122
123
125 camera_.reset();
126 drawable_axes_.reset();
127 texter_.reset();
128
130
133 delete gl_context_;
134
135 LOG(INFO) << "viewer terminated. Bye!";
136 }
137
138
139 void Viewer::init() {
140
142
143
144 glGetError();
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);
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
164
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
171 texter_ = std::unique_ptr<TextRenderer>(
new TextRenderer(
dpi_scaling()));
174
175#if 1
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);
186 LOG(INFO) << "program initialized by creating a SurfaceMesh of the bunny model";
187#endif
188 }
189
190
192 models_.clear();
193 drawables_.clear();
194 }
195
196
198 const_cast<Viewer*
>(
this)->Refresh();
199 }
200
201
202 void Viewer::OnPaint(wxPaintEvent & WXUNUSED(event)) {
203
204 wxPaintDC dc(this);
205
206 SetCurrent(*gl_context_);
207
208
209 if (!initialized_) {
210 init();
211 initialized_ = true;
212 }
213
214 pre_draw();
215 draw();
216 post_draw();
217
218
219 glFlush();
220
221
222 SwapBuffers();
223 }
224
225
226 void Viewer::OnSize(wxSizeEvent & event) {
227
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)
257 else if (right_down)
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) {
279 } else if (event.GetUnicodeKey() == wxKeyCode('F') && event.GetModifiers() == wxMOD_NONE) {
281 } else if (event.GetUnicodeKey() == wxKeyCode('M') && event.GetModifiers() == wxMOD_NONE) {
285 d->set_smooth_shading(!d->smooth_shading());
286 }
287 } else if (event.GetUnicodeKey() == wxKeyCode('P') && event.GetModifiers() == wxMOD_NONE) {
290 else
292 } else if (event.GetUnicodeKey() == WXK_SPACE && event.GetModifiers() == wxMOD_NONE) {
293
294 Frame frame;
295 frame.setTranslation(camera_->pivotPoint());
296 camera_->frame()->alignWithFrame(&frame, true);
297
298
299
300
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) {
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) {
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) {
361 }
362 else if (event.GetUnicodeKey() == wxKeyCode('E') && event.GetModifiers() == wxMOD_NONE) {
365 if (edges)
366 edges->set_visible(!edges->is_visible());
367 }
368 }
369 else if (event.GetUnicodeKey() == wxKeyCode('V') && event.GetModifiers() == wxMOD_NONE) {
372 if (vertices)
373 vertices->set_visible(!vertices->is_visible());
374 }
375 }
376 else if (event.GetUnicodeKey() == wxKeyCode('B') && event.GetModifiers() == wxMOD_NONE) {
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) {
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) {
394 auto &output = std::cout;
395
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;
404 output << "model is a point cloud. #vertex: " + std::to_string(model->n_vertices()) << std::endl;
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";
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
428 }
429 }
430
431 else if (event.GetUnicodeKey() == wxKeyCode('R') && event.GetModifiers() == wxMOD_NONE) {
432
434 }
435
437 }
438
439
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
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();
468 }
469 }
470
471
473 return static_cast<float>(wxWindow::GetContentScaleFactor());
474 }
475
476
477 Model *
Viewer::add_model(
const std::string &file_path,
bool create_default_drawables) {
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
487 bool is_ply_mesh = false;
488 if (ext == "ply")
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") {
497 } else if (ext == "plm" || ext == "pm" || ext == "mesh") {
499 } else {
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;
508 } else
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
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;
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
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;
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
569 LOG(WARNING) << "no such model: " << model->name();
570 return false;
571 }
572
573
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 {
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))
593 else if (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
606 SetCurrent(*gl_context_);
607
608 int x, y, w, h;
610
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
628 return fbo.snapshot_color(0, file_name);
629#else
630
631 return fbo.snapshot_depth(file_name);
632#endif
633 }
634
635
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
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
667 LOG(WARNING) << "no such drawable: " << drawable->name();
668 return false;
669 }
670
671
672 void Viewer::draw_corner_axes() const {
674 if (!program) {
675 std::vector<ShaderProgram::Attribute> attributes = {
680 };
682 }
683 if (!program)
684 return;
685
686 if (!drawable_axes_) {
687 float base = 0.5f;
688 float head = 0.2f;
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);
705 }
706 if (!drawable_axes_->is_visible())
707 return;
708
709
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
717
718 glDepthRange(0, 0.01f);
719
721 const mat4 &view = camera_->orientation().inverse().matrix();
722 const mat4 &MVP = proj * view;
723
724
725 const vec3 &wCamPos = camera_->position();
726
727
728 const mat4 &MV = camera_->modelViewMatrix();
730
731 program->bind();
732 program->set_uniform("MVP", MVP)
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)
746 ->set_uniform("highlight", false)
747 ->set_uniform("clippingPlaneEnabled", false)
748 ->set_uniform("selected", false)
750 ->set_uniform("use_texture", false);
751 drawable_axes_->gl_draw();
752 program->release();
753
754
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
769 if (texter_ && texter_->num_fonts() >=2) {
770 const float font_size = 15.0f;
772 texter_->draw("Easy3D", offset, offset, font_size, 0);
773 }
774
775
776 draw_corner_axes();
777 }
778
779
780 void Viewer::draw() const {
781#if 0
782
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())
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())
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
805
806
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())
835 }
836#endif
837 }
838
839
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 ¢er, 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
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
43
44
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
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
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
76 auto viewMenu = new wxMenu;
77 viewMenu->Append(VIEW_FIT_SCREEN, "&Fit screen\tF");
78 viewMenu->Append(VIEW_SNAPSHOT, "&Snapshot...\tS");
79
80
81 auto editMenu = new wxMenu;
82 editMenu->Append(EDIT_SUBDIVISION, "&Subdivision");
83
84
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
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
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();
132 name += ".ply";
133
134 const std::string &title = "Please specify a file 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
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())
159
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
173 void Window::menuFileExit(wxCommandEvent & WXUNUSED(event)) {
174
175 Close(true);
176 }
177
178
179 void Window::menuViewFitScreen(wxCommandEvent &) {
180 viewer_->fit_screen();
181 }
182
183
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
197 void Window::menuHelpAbout(wxCommandEvent & WXUNUSED(event)) {
198 wxMessageBox("Easy3D viewer based on wxWidgets");
199 }
200
201
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,...