This example shows how to create a simple viewer using Qt.
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 "viewer.h"
28
29#include <QKeyEvent>
30#include <QPainter>
31#include <QOpenGLContext>
32#include <QOpenGLFunctions>
33#include <QOpenGLFramebufferObjectFormat>
34#include <QApplication>
35
36#include <easy3d/core/surface_mesh.h>
37#include <easy3d/core/point_cloud.h>
38#include <easy3d/core/graph.h>
39#include <easy3d/renderer/drawable_points.h>
40#include <easy3d/renderer/drawable_lines.h>
41#include <easy3d/renderer/drawable_triangles.h>
42#include <easy3d/renderer/shader_program.h>
43#include <easy3d/renderer/shader_manager.h>
44#include <easy3d/renderer/shape.h>
45#include <easy3d/renderer/transform.h>
46#include <easy3d/renderer/camera.h>
47#include <easy3d/renderer/manipulated_camera_frame.h>
48#include <easy3d/renderer/key_frame_interpolator.h>
49#include <easy3d/renderer/opengl_util.h>
50#include <easy3d/renderer/opengl_error.h>
51#include <easy3d/renderer/texture_manager.h>
52#include <easy3d/renderer/text_renderer.h>
53#include <easy3d/renderer/renderer.h>
54#include <easy3d/util/resource.h>
55#include <easy3d/fileio/surface_mesh_io.h>
56#include <easy3d/util/logging.h>
57#include <easy3d/util/file_system.h>
58#include <easy3d/util/setting.h>
59
60
62
63
64
66 : QOpenGLWidget(parent), texter_(nullptr), pressed_button_(Qt::NoButton),
67 mouse_pressed_pos_(0, 0), mouse_previous_pos_(0, 0), show_pivot_point_(false), drawable_axes_(nullptr),
68 model_idx_(-1) {
69
70 setFocusPolicy(Qt::WheelFocus);
71 setMouseTracking(true);
72
73 camera_ = new Camera;
75 camera_->setUpVector(
vec3(0, 0, 1));
76 camera_->setViewDirection(
vec3(-1, 0, 0));
77 camera_->showEntireScene();
78
80 }
81
82
84
85
86 makeCurrent();
87
88 cleanup();
89
90 doneCurrent();
91
92 LOG(INFO) << "viewer terminated. Bye!";
93 }
94
95
96 void Viewer::cleanup() {
97 delete camera_;
98 delete drawable_axes_;
99 delete texter_;
100
101 for (auto m: models_)
102 delete m;
103 models_.clear();
104
105 for (auto d : drawables_)
106 delete d;
107 drawables_.clear();
108
111 }
112
113
114 void Viewer::init() {
117 if (mesh)
118 addModel(mesh);
119
120
123 fitScreen(mesh);
124 }
125
126
127 void Viewer::initializeGL() {
128 QOpenGLWidget::initializeGL();
129 initializeOpenGLFunctions();
130
132#ifndef NDEBUG
134#endif
135
136 if (!hasOpenGLFeature(QOpenGLFunctions::Multisample))
137 throw std::runtime_error("Multisample not supported on this machine!!! Viewer may not run properly");
138 if (!hasOpenGLFeature(QOpenGLFunctions::Framebuffers))
139 throw std::runtime_error(
140 "Framebuffer Object is not supported on this machine!!! Viewer may not run properly");
141
143
144 glEnable(GL_DEPTH_TEST);
145 glClearDepthf(1.0f);
146 glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
147
148 int major_requested = QSurfaceFormat::defaultFormat().majorVersion();
149 int minor_requested = QSurfaceFormat::defaultFormat().minorVersion();
150 VLOG(1) << "OpenGL vendor: " << glGetString(GL_VENDOR);
151 VLOG(1) << "OpenGL renderer: " << glGetString(GL_RENDERER);
152 VLOG(1) << "OpenGL version requested: " << major_requested << "." << minor_requested;
153 VLOG(1) << "OpenGL version received: " << glGetString(GL_VERSION);
154 VLOG(1) << "GLSL version received: " << glGetString(GL_SHADING_LANGUAGE_VERSION);
155
156 int major = 0;
157 glGetIntegerv(GL_MAJOR_VERSION, &major);
158 int minor = 0;
159 glGetIntegerv(GL_MINOR_VERSION, &minor);
160 if (major * 10 + minor < 32) {
161 throw std::runtime_error("Viewer requires at least OpenGL 3.2");
162 }
163
164#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
165 dpi_scaling_ = static_cast<float>(devicePixelRatioF());
166#else
167 dpi_scaling_ = static_cast<float>(devicePixelRatio());
168#endif
169 VLOG(1) << "DPI scaling: " << dpiScaling();
170
171
172
173
174
175
176
177
178 texter_ = new TextRenderer(dpiScaling());
181
182
183 init();
184
185
186 std::cout << usage() << std::endl;
187
188 timer_.start();
189 }
190
191
192 void Viewer::resizeGL(int w, int h) {
193 QOpenGLWidget::resizeGL(w, h);
194
195
196
197
198
200 }
201
202
203 void Viewer::setBackgroundColor(
const vec4 &c) {
204 background_color_ = c;
205
206 makeCurrent();
207 glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
208 doneCurrent();
209 }
210
211
212 void Viewer::mousePressEvent(QMouseEvent *e) {
213 pressed_button_ = e->button();
214 mouse_previous_pos_ = e->pos();
215 mouse_pressed_pos_ = e->pos();
216
217 camera_->frame()->action_start();
218 if (e->modifiers() == Qt::ShiftModifier) {
219 if (e->button() == Qt::LeftButton) {
220 bool found = false;
221 const vec3 &p = pointUnderPixel(e->pos(), found);
222 if (found) {
224 camera_->setPivotPoint(p);
225
226
227 show_pivot_point_ = true;
229 show_pivot_point_ = false;
231 });
232 } else {
233 camera_->setPivotPoint(camera_->sceneCenter());
234 show_pivot_point_ = false;
235 }
236 } else if (e->button() == Qt::RightButton) {
238 camera_->setPivotPoint(camera_->sceneCenter());
239 show_pivot_point_ = false;
240 }
241 }
242
243 QOpenGLWidget::mousePressEvent(e);
245 }
246
247
248 void Viewer::mouseReleaseEvent(QMouseEvent *e) {
249 if (e->button() == Qt::LeftButton && e->modifiers() == Qt::ControlModifier) {
250 int xmin = std::min(mouse_pressed_pos_.x(), e->pos().x());
251 int xmax = std::max(mouse_pressed_pos_.x(), e->pos().x());
252 int ymin = std::min(mouse_pressed_pos_.y(), e->pos().y());
253 int ymax = std::max(mouse_pressed_pos_.y(), e->pos().y());
254 camera_->fitScreenRegion(xmin, ymin, xmax, ymax);
255 } else
256 camera_->frame()->action_end();
257
258 pressed_button_ = Qt::NoButton;
259 mouse_pressed_pos_ = QPoint(0, 0);
260
261 QOpenGLWidget::mouseReleaseEvent(e);
263 }
264
265
266 void Viewer::mouseMoveEvent(QMouseEvent *e) {
267 int x = e->pos().x(), y = e->pos().y();
268
269 if (x < 0 || x >
width() || y < 0 || y >
height()) {
270 e->ignore();
271 return;
272 }
273
274 if (pressed_button_ != Qt::NoButton) {
275 if (e->modifiers() == Qt::ControlModifier) {
276
277 } else {
278 int dx = x - mouse_previous_pos_.x();
279 int dy = y - mouse_previous_pos_.y();
280 if (pressed_button_ == Qt::LeftButton)
282 else if (pressed_button_ == Qt::RightButton)
284 else if (pressed_button_ == Qt::MiddleButton) {
285 if (dy != 0)
286 camera_->frame()->action_zoom(dy > 0 ? 1 : -1, camera_);
287 }
288 }
289 }
290
291 mouse_previous_pos_ = e->pos();
292 QOpenGLWidget::mouseMoveEvent(e);
293 }
294
295
296 void Viewer::mouseDoubleClickEvent(QMouseEvent *e) {
297 QOpenGLWidget::mouseDoubleClickEvent(e);
299 }
300
301
302 void Viewer::wheelEvent(QWheelEvent *e) {
303 const int delta = e->angleDelta().y();
304 if (delta <= -1 || delta >= 1) {
305 int dy = e->angleDelta().y() > 0 ? 1 : -1;
306 camera_->frame()->action_zoom(dy, camera_);
307 }
308
309 QOpenGLWidget::wheelEvent(e);
311 }
312
313
314 bool Viewer::saveSnapshot(const QString &file_name) {
315 makeCurrent();
316
317 int w =
static_cast<int>(
static_cast<float>(
width()) * dpiScaling());
318 int h =
static_cast<int>(
static_cast<float>(
height()) * dpiScaling());
319
320 QOpenGLFramebufferObjectFormat format;
321 format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
322 format.setSamples(4);
323 auto fbo = new QOpenGLFramebufferObject(w, h, format);
324 fbo->addColorAttachment(w, h);
325
326 fbo->bind();
327 glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
328 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
329
330 draw();
331
332 fbo->release();
333
334 const QImage &image = fbo->toImage();
335
336
337 glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
338
339 doneCurrent();
340
341 return image.save(file_name);
342 }
343
344
345 Model *Viewer::currentModel() const {
346 if (models_.empty())
347 return nullptr;
348 if (model_idx_ < models_.size())
349 return models_[model_idx_];
350 return nullptr;
351 }
352
353 void Viewer::keyPressEvent(QKeyEvent *e) {
354 if (e->key() == Qt::Key_F1 && e->modifiers() == Qt::NoModifier)
355 std::cout << usage() << std::endl;
356 else if (e->key() == Qt::Key_Left && e->modifiers() == Qt::KeypadModifier) {
357 auto angle = static_cast<float>(1 * M_PI / 180.0);
358 camera_->frame()->action_turn(angle, camera_);
359 } else if (e->key() == Qt::Key_Right && e->modifiers() == Qt::KeypadModifier) {
360 auto angle = static_cast<float>(1 * M_PI / 180.0);
361 camera_->frame()->action_turn(-angle, camera_);
362 } else if (e->key() == Qt::Key_Up && e->modifiers() == Qt::KeypadModifier) {
363 float step = 0.05f * camera_->sceneRadius();
364 camera_->frame()->translate(camera_->frame()->inverseTransformOf(
vec3(0.0, 0.0, -step)));
365 } else if (e->key() == Qt::Key_Down && e->modifiers() == Qt::KeypadModifier) {
366 float step = 0.05f * camera_->sceneRadius();
367 camera_->frame()->translate(camera_->frame()->inverseTransformOf(
vec3(0.0, 0.0, step)));
368 } else if (e->key() == Qt::Key_Left &&
369 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) {
370 float step = 0.05f * camera_->sceneRadius();
371 camera_->frame()->translate(camera_->frame()->inverseTransformOf(
vec3(-step, 0.0, 0.0)));
372 } else if (e->key() == Qt::Key_Right &&
373 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) {
374 float step = 0.05f * camera_->sceneRadius();
375 camera_->frame()->translate(camera_->frame()->inverseTransformOf(
vec3(step, 0.0, 0.0)));
376 } else if (e->key() == Qt::Key_Up &&
377 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) {
378 float step = 0.05f * camera_->sceneRadius();
379 camera_->frame()->translate(camera_->frame()->inverseTransformOf(
vec3(0.0, step, 0.0)));
380 } else if (e->key() == Qt::Key_Down &&
381 e->modifiers() == (Qt::KeypadModifier | Qt::ControlModifier)) {
382 float step = 0.05f * camera_->sceneRadius();
383 camera_->frame()->translate(camera_->frame()->inverseTransformOf(
vec3(0.0, -step, 0.0)));
384 } else if (e->key() == Qt::Key_A && e->modifiers() == Qt::NoModifier) {
385 if (drawable_axes_)
386 drawable_axes_->set_visible(!drawable_axes_->is_visible());
387 } else if (e->key() == Qt::Key_C && e->modifiers() == Qt::NoModifier) {
388 if (currentModel())
389 fitScreen(currentModel());
390 } else if (e->key() == Qt::Key_F && e->modifiers() == Qt::NoModifier) {
391 fitScreen();
392 } else if (e->key() == Qt::Key_P && e->modifiers() == Qt::NoModifier) {
395 else
397 } else if (e->key() == Qt::Key_Space && e->modifiers() == Qt::NoModifier) {
398
399 Frame frame;
400 frame.setTranslation(camera_->pivotPoint());
401 camera_->frame()->alignWithFrame(&frame, true);
402
403
404
405
406 } else if (e->key() == Qt::Key_Minus && e->modifiers() == Qt::ControlModifier)
407 camera_->frame()->action_zoom(-1, camera_);
408 else if (e->key() == Qt::Key_Equal && e->modifiers() == Qt::ControlModifier)
409 camera_->frame()->action_zoom(1, camera_);
410
411 else if (e->key() == Qt::Key_K && e->modifiers() == Qt::AltModifier) {
414
418 } else if (e->key() == Qt::Key_D && e->modifiers() == Qt::ControlModifier) {
420
421
423 for (auto m: models_)
424 box.
grow(m->bounding_box());
425 camera_->setSceneBoundingBox(box.min_point(), box.max_point());
426 } else if (e->key() == Qt::Key_K && e->modifiers() == Qt::ControlModifier) {
427 if (
camera()->keyframe_interpolator()->is_interpolation_started())
429 else
431 } else if (e->key() == Qt::Key_BracketLeft && e->modifiers() == Qt::NoModifier) {
432 for (auto m: models_) {
433 for (auto d: m->renderer()->lines_drawables()) {
434 float size = d->line_width() - 1.0f;
435 if (size < 1)
436 size = 1;
437 d->set_line_width(size);
438 }
439 }
440 } else if (e->key() == Qt::Key_BracketRight && e->modifiers() == Qt::NoModifier) {
441 for (auto m: models_) {
442 for (auto d: m->renderer()->lines_drawables()) {
443 float size = d->line_width() + 1.0f;
444 d->set_line_width(size);
445 }
446 }
447 } else if (e->key() == Qt::Key_Minus && e->modifiers() == Qt::NoModifier) {
448 for (auto m: models_) {
449 for (auto d: m->renderer()->points_drawables()) {
450 float size = d->point_size() - 1.0f;
451 if (size < 1)
452 size = 1;
453 d->set_point_size(size);
454 }
455 }
456 } else if (e->key() == Qt::Key_Equal && e->modifiers() == Qt::NoModifier) {
457 for (auto m: models_) {
458 for (auto d: m->renderer()->points_drawables()) {
459 float size = d->point_size() + 1.0f;
460 d->set_point_size(size);
461 }
462 }
463 } else if (e->key() == Qt::Key_Comma && e->modifiers() == Qt::NoModifier) {
464 int pre_idx = model_idx_;
465 if (models_.empty())
466 model_idx_ = -1;
467 else
468 model_idx_ = int((model_idx_ - 1 + models_.size()) % models_.size());
469 if (model_idx_ != pre_idx) {
470 if (model_idx_ >= 0)
471 LOG(INFO) << "current model: " << model_idx_ << ", " << models_[model_idx_]->name();
472 }
473 } else if (e->key() == Qt::Key_Period && e->modifiers() == Qt::NoModifier) {
474 int pre_idx = model_idx_;
475 if (models_.empty())
476 model_idx_ = -1;
477 else
478 model_idx_ = int((model_idx_ + 1) % models_.size());
479 if (model_idx_ != pre_idx) {
480 if (model_idx_ >= 0)
481 LOG(INFO) << "current model: " << model_idx_ << ", " << models_[model_idx_]->name();
482 }
483 } else if (e->key() == Qt::Key_Delete && e->modifiers() == Qt::NoModifier) {
484 if (currentModel())
485 deleteModel(currentModel());
486 } else if (e->key() == Qt::Key_E && e->modifiers() == Qt::NoModifier) {
487 if (currentModel()) {
488 auto *drawable = currentModel()->renderer()->get_lines_drawable("edges");
489 if (drawable)
490 drawable->set_visible(!drawable->is_visible());
491 }
492 } else if (e->key() == Qt::Key_V && e->modifiers() == Qt::NoModifier) {
493 if (currentModel()) {
494 auto drawable = currentModel()->renderer()->get_points_drawable("vertices");
495 if (drawable)
496 drawable->set_visible(!drawable->is_visible());
497 }
498 } else if (e->key() == Qt::Key_B && e->modifiers() == Qt::NoModifier) {
499 auto mesh = dynamic_cast<SurfaceMesh *>(currentModel());
500 if (mesh) {
501 auto drawable = mesh->renderer()->get_lines_drawable("borders");
502 if (drawable)
503 drawable->set_visible(!drawable->is_visible());
504 }
505 } else if (e->key() == Qt::Key_L && e->modifiers() == Qt::NoModifier) {
506 auto mesh = dynamic_cast<SurfaceMesh *>(currentModel());
507 if (mesh) {
508 auto drawable = mesh->renderer()->get_points_drawable("locks");
509 if (drawable)
510 drawable->set_visible(!drawable->is_visible());
511 }
512 } else if (e->key() == Qt::Key_M && e->modifiers() == Qt::NoModifier) {
513 if (dynamic_cast<SurfaceMesh *>(currentModel())) {
514 auto drawable = currentModel()->renderer()->get_triangles_drawable("faces");
515 if (drawable) {
516 drawable->set_smooth_shading(!drawable->smooth_shading());
517 }
518 }
519 } else if (e->key() == Qt::Key_D && e->modifiers() == Qt::NoModifier) {
520 if (currentModel()) {
521 std::ostream &output = std::cout;
522
524 if (dynamic_cast<SurfaceMesh *>(currentModel())) {
525 auto model = dynamic_cast<SurfaceMesh *>(currentModel());
526 output << "model is a surface mesh. #face: " << std::to_string(model->n_faces())
527 << ", #vertex: " + std::to_string(model->n_vertices())
528 << ", #edge: " + std::to_string(model->n_edges()) << std::endl;
529 } else if (dynamic_cast<PointCloud *>(currentModel())) {
530 auto model = dynamic_cast<PointCloud *>(currentModel());
531 output << "model is a point cloud. #vertex: " + std::to_string(model->n_vertices()) << std::endl;
532 } else if (dynamic_cast<Graph *>(currentModel())) {
533 auto model = dynamic_cast<Graph *>(currentModel());
534 output << "model is a graph. #vertex: " + std::to_string(model->n_vertices())
535 << ", #edge: " + std::to_string(model->n_edges()) << std::endl;
536 }
537
538 if (!currentModel()->renderer()->points_drawables().empty()) {
539 output << "points drawables:\n";
540 for (auto d: currentModel()->renderer()->points_drawables())
541 d->buffer_stats(output);
542 }
543 if (!currentModel()->renderer()->lines_drawables().empty()) {
544 output << "lines drawables:\n";
545 for (auto d: currentModel()->renderer()->lines_drawables())
546 d->buffer_stats(output);
547 }
548 if (!currentModel()->renderer()->triangles_drawables().empty()) {
549 output << "triangles drawables:\n";
550 for (auto d: currentModel()->renderer()->triangles_drawables())
551 d->buffer_stats(output);
552 }
553
554 currentModel()->property_stats(output);
555 }
556 } else if (e->key() == Qt::Key_R && e->modifiers() == Qt::NoModifier) {
557
559 }
560
561 QOpenGLWidget::keyPressEvent(e);
563 }
564
565
566 void Viewer::keyReleaseEvent(QKeyEvent *e) {
567 QOpenGLWidget::keyReleaseEvent(e);
569 }
570
571
572 void Viewer::timerEvent(QTimerEvent *e) {
573 QOpenGLWidget::timerEvent(e);
575 }
576
577
578 void Viewer::closeEvent(QCloseEvent *e) {
579 cleanup();
580 QOpenGLWidget::closeEvent(e);
581 }
582
583
584 std::string Viewer::usage() const {
585 return " ------------------------------------------------------------------\n"
586 " Easy3D viewer usage: \n"
587 " ------------------------------------------------------------------\n"
588 " F1: Help \n"
589 " ------------------------------------------------------------------\n"
590 " Ctrl + 'o': Open file \n"
591 " Ctrl + 's': Save file \n"
592 " Fn + Delete: Delete current model \n"
593 " '<' or '>': Switch between models \n"
594 " 's': Snapshot \n"
595 " ------------------------------------------------------------------\n"
596 " 'p': Toggle perspective/orthographic projection) \n"
597 " Left: Orbit-rotate the camera \n"
598 " Right: Move up/down/left/right \n"
599 " Middle or Wheel: Zoom in/out \n"
600 " Ctrl + '+'/'-': Zoom in/out \n"
601 " Alt + Left: Orbit-rotate the camera (screen based) \n"
602 " Alt + Right: Move up/down/left/right (screen based) \n"
603 " Left/Right Turn camera left/right \n"
604 " Ctrl + Left/Right: Move camera left/right \n"
605 " Up/Down: Move camera forward/backward \n"
606 " Ctrl + Up/Down: Move camera up/down \n"
607 " ------------------------------------------------------------------\n"
608 " 'f': Fit screen (all models) \n"
609 " 'c': Fit screen (current model only) \n"
610 " Shift + Left/Right: Zoom to target/Zoom to fit screen \n"
611 " ------------------------------------------------------------------\n"
612 " '+'/'-': Increase/Decrease point size (line width) \n"
613 " 'a': Toggle axes \n"
614 " 'b': Toggle borders \n"
615 " 'e': Toggle edges \n"
616 " 'v': Toggle vertices \n"
617 " 'm': Toggle smooth shading (for SurfaceMesh) \n"
618 " 'd': Print model info (drawables, properties) \n"
619 " ------------------------------------------------------------------\n";
620 }
621
622
623 void Viewer::addModel(Model *model) {
624 if (!model) {
625 LOG(WARNING) << "model is nullptr";
626 return;
627 }
628 for (auto m: models_) {
629 if (model == m) {
630 LOG(WARNING) << "model has already been added to the viewer.";
631 return;
632 }
633 }
634
635 if (model->empty()) {
636 LOG(WARNING) << "model does not have vertices. Only complete model can be added to the viewer.";
637 return;
638 }
639
640 makeCurrent();
641 model->set_renderer(std::make_shared<Renderer>(model));
642 doneCurrent();
643
644 int pre_idx = model_idx_;
645 models_.push_back(model);
646 model_idx_ = static_cast<int>(models_.size()) - 1;
647
648 if (model_idx_ != pre_idx) {
649 emit currentModelChanged();
650 if (model_idx_ >= 0)
651 LOG(INFO) << "current model: " << model_idx_ << ", " << models_[model_idx_]->name();
652 }
653 }
654
655
656 void Viewer::deleteModel(Model *model) {
657 if (!model) {
658 LOG(WARNING) << "model is nullptr";
659 return;
660 }
661
662 int pre_idx = model_idx_;
663 auto pos = std::find(models_.begin(), models_.end(), model);
664 if (pos != models_.end()) {
665 const std::string name = model->name();
666 models_.erase(pos);
667 makeCurrent();
668 delete model;
669 doneCurrent();
670 model_idx_ = static_cast<int>(models_.size()) - 1;
671
672 std::cout << "model deleted: " << name << std::endl;
673 } else
674 LOG(WARNING) << "no such model: " << model->name();
675
676 if (model_idx_ != pre_idx) {
677 emit currentModelChanged();
678 if (model_idx_ >= 0)
679 LOG(INFO) << "current model: " << model_idx_ << ", " << models_[model_idx_]->name();
680 }
681 }
682
683
684 bool Viewer::addDrawable(Drawable *drawable) {
685 if (!drawable) {
686 LOG(WARNING) << "drawable is nullptr";
687 return false;
688 }
689 for (auto d : drawables_) {
690 if (drawable == d) {
691 LOG(WARNING) << "drawable has already been added to the viewer.";
692 return false;
693 }
694 }
695
696 drawables_.push_back(drawable);
697 return true;
698 }
699
700
701 bool Viewer::deleteDrawable(Drawable *drawable) {
702 if (!drawable) {
703 LOG(WARNING) << "drawable is nullptr";
704 return false;
705 }
706
707 for (auto it = drawables_.begin(); it != drawables_.end(); ++it) {
708 if (*it == drawable) {
709 delete drawable;
710 drawables_.erase(it);
711 return true;
712 }
713 }
714
715
716 LOG(WARNING) << "no such drawable: " << drawable->name();
717 return false;
718 }
719
720
721 void Viewer::fitScreen(const Model *model) {
722 if (!model && models_.empty() && drawables_.empty())
723 return;
724
726 if (model)
727 box = model->bounding_box();
728 else {
729 for (auto m: models_)
730 box.grow(m->bounding_box());
731 for (auto d : drawables_)
732 box.grow(d->bounding_box());
733 }
734 camera_->setSceneBoundingBox(box.min_point(), box.max_point());
735 camera_->showEntireScene();
737 }
738
739
740 vec3 Viewer::pointUnderPixel(
const QPoint &p,
bool &found) {
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762 makeCurrent();
763
764 int viewport[4];
765 glGetIntegerv(GL_VIEWPORT, viewport);
766 const int w = viewport[2];
767 const int h = viewport[3];
768
769
770 QOpenGLFramebufferObjectFormat format;
771 format.setAttachment(QOpenGLFramebufferObject::Depth);
772 format.setSamples(0);
773 QOpenGLFramebufferObject fbo(w, h, format);
774
775
776 fbo.bind();
777 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
778 std::cerr << "Framebuffer is not complete!" << std::endl;
779 return vec3(0, 0, 0);
780 }
781
782
783 GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST);
784 if (!last_enable_depth_test)
785 glEnable(GL_DEPTH_TEST);
786 glClear(GL_DEPTH_BUFFER_BIT);
787
788
789 draw();
790
791
792
793 int glx = p.x();
794 int gly =
height() - 1 - p.y();
795
796 glx = static_cast<int>(static_cast<float>(glx) * dpiScaling());
797 gly = static_cast<int>(static_cast<float>(gly) * dpiScaling());
798 float depth = std::numeric_limits<float>::max();
799 glReadPixels(glx, gly, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);
800
801
802 fbo.release();
803
804
805 if (!last_enable_depth_test)
806 glDisable(GL_DEPTH_TEST);
807
808 doneCurrent();
809
810 found = depth < 1.0f;
811 if (found) {
812
813 return camera_->unprojectedCoordinatesOf(
vec3(
static_cast<float>(p.x()),
static_cast<float>(p.y()), depth));
814 }
815
816 return {};
817}
818
819
820 void Viewer::paintGL() {
821 easy3d_debug_log_gl_error
822
823#if 1
824
825
826
827
828
829
830 static bool queried = false;
831 if (!queried) {
832#if 1
833 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples_);
834 easy3d_debug_log_frame_buffer_error
835#else
836 glGetIntegerv(GL_SAMPLES, &samples_); easy3d_debug_log_gl_error
837#endif
838
839 int samples = QSurfaceFormat::defaultFormat().samples();
840 int max_num = 0;
841 glGetIntegerv(GL_MAX_SAMPLES, &max_num);
843 if (samples_ == 0)
844 LOG(WARNING) <<
"MSAA is not available (" <<
samples <<
" samples requested)";
845 else
846 LOG(WARNING) <<
"MSAA is available with " << samples_ <<
" samples (" <<
samples
847 << " requested but max support is " << max_num << ")";
848 }
else VLOG(1) <<
"Samples received: " << samples_ <<
" (" <<
samples <<
" requested, max support is "
849 << max_num << ")";
850
851 queried = true;
852 }
853#endif
854
855 preDraw();
856
857 draw();
858
859
860 postDraw();
861 }
862
863
864 void Viewer::drawCornerAxes() {
866 if (!program) {
867 std::vector<ShaderProgram::Attribute> attributes;
873 }
874 if (!program)
875 return;
876
877 if (!drawable_axes_) {
878 const float base = 0.5f;
879 const float head = 0.2f;
880 std::vector<vec3> points, normals, colors;
881 shape::create_cylinder(0.03, 10,
vec3(0, 0, 0),
vec3(base, 0, 0),
vec3(1, 0, 0), points, normals, colors);
882 shape::create_cylinder(0.03, 10,
vec3(0, 0, 0),
vec3(0, base, 0),
vec3(0, 1, 0), points, normals, colors);
883 shape::create_cylinder(0.03, 10,
vec3(0, 0, 0),
vec3(0, 0, base),
vec3(0, 0, 1), points, normals, colors);
884 shape::create_cone(0.06, 20,
vec3(base, 0, 0),
vec3(base + head, 0, 0),
vec3(1, 0, 0), points, normals,
885 colors);
886 shape::create_cone(0.06, 20,
vec3(0, base, 0),
vec3(0, base + head, 0),
vec3(0, 1, 0), points, normals,
887 colors);
888 shape::create_cone(0.06, 20,
vec3(0, 0, base),
vec3(0, 0, base + head),
vec3(0, 0, 1), points, normals,
889 colors);
890 shape::create_sphere(
vec3(0, 0, 0), 0.06, 20, 20,
vec3(0, 1, 1), points, normals, colors);
891 drawable_axes_ = new TrianglesDrawable("corner_axes");
892 drawable_axes_->update_vertex_buffer(points);
893 drawable_axes_->update_normal_buffer(normals);
894 drawable_axes_->update_color_buffer(colors);
896 }
897 if (!drawable_axes_->is_visible())
898 return;
899
900
901 int viewport[4];
902 glGetIntegerv(GL_VIEWPORT, viewport);
903
904 static const int corner_frame_size = static_cast<int>(100 * dpiScaling());
905 glViewport(0, 0, corner_frame_size, corner_frame_size);
906
907
908
909 glDepthRangef(0, 0.01f);
910
912 const mat4 &view = camera_->orientation().inverse().matrix();
913 const mat4 &MVP = proj * view;
914
915
916 const vec3 &wCamPos = camera_->position();
917
918
919 const mat4 &MV = camera_->modelViewMatrix();
921
922 program->bind();
923 program->set_uniform("MVP", MVP)
926 ->set_uniform("lighting", true)
927 ->set_uniform("two_sides_lighting", false)
928 ->set_uniform("smooth_shading", true)
929 ->set_uniform("wLightPos", wLightPos)
930 ->set_uniform("wCamPos", wCamPos)
931 ->set_uniform("ssaoEnabled", false)
932 ->set_uniform("per_vertex_color", true)
933 ->set_uniform("distinct_back_color", false)
937 ->set_uniform("highlight", false)
938 ->set_uniform("clippingPlaneEnabled", false)
939 ->set_uniform("selected", false)
941 ->set_uniform("use_texture", false);
942 drawable_axes_->gl_draw();
943 program->release();
944
945
946 glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
947 glDepthRangef(0.0f, 1.0f);
948 }
949
950
951 void Viewer::preDraw() {
952
953
954
955 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
956 }
957
958
959 void Viewer::postDraw() {
960
961 if (texter_ && texter_->num_fonts() >= 2) {
962 const float font_size = 15.0f;
963 const float offset = 20.0f * dpiScaling();
964 texter_->draw("Easy3D", offset, offset, font_size, 0);
965
966
967 static unsigned int fps_count = 0;
968 static double fps = 0.0;
969 static const unsigned int max_count = 40;
970 static QString fps_string("fps: ??");
971 if (++fps_count == max_count) {
972 fps = 1000.0 * max_count / static_cast<double>(timer_.restart());
973 fps_string = tr("fps: %1").arg(fps, 0, 'f', ((fps < 10.0) ? 1 : 0));
974 fps_count = 0;
975 }
976 texter_->draw(fps_string.toStdString(), offset, 50.0f * dpiScaling(), 16, 1);
977 }
978
979 if (show_pivot_point_) {
981 if (!program) {
982 std::vector<ShaderProgram::Attribute> attributes;
986 }
987 if (!program)
988 return;
989
990 const float size = 10;
991 LinesDrawable drawable("pivot_point");
993 std::vector<vec3> points = {
994 vec3(pivot.x - size, pivot.y, 0.5f),
vec3(pivot.x + size, pivot.y, 0.5f),
995 vec3(pivot.x, pivot.y - size, 0.5f),
vec3(pivot.x, pivot.y + size, 0.5f)
996 };
997 drawable.update_vertex_buffer(points);
998
1000 0.0f,
1001 -1.0f);
1002 glDisable(GL_DEPTH_TEST);
1003 program->bind();
1004 program->set_uniform("MVP", proj);
1005 program->set_uniform("per_vertex_color", false);
1006 program->set_uniform(
"default_color",
vec4(0.0f, 0.0f, 1.0f, 1.0f));
1007 drawable.gl_draw();
1008 program->release();
1009 glEnable(GL_DEPTH_TEST);
1010 }
1011
1012 drawCornerAxes();
1013 }
1014
1015
1016 void Viewer::draw() {
1017#if 0
1018
1019 glEnable(GL_SCISSOR_TEST);
1020 int viewport[4];
1021 glGetIntegerv(GL_VIEWPORT, viewport);
1022 glScissor(viewport[0], viewport[1], viewport[2] * 0.5f, viewport[3]);
1023 for (const auto m : models_) {
1024 for (auto d : m->renderer()->triangles_drawables())
1026 }
1027 glScissor(viewport[2] * 0.5f, viewport[1], viewport[2] * 0.5f, viewport[3]);
1028 for (const auto m : models_) {
1029 for (auto d : m->renderer()->lines_drawables())
1031 }
1032 glScissor(viewport[0], viewport[1], viewport[2], viewport[3]);
1033
1034#else
1035
1036 easy3d_debug_log_gl_error
1037
1038 for (const auto m: models_) {
1039 if (!m->renderer()->is_visible())
1040 continue;
1041
1042
1043 glDepthRange(0.001, 1.0);
1044 for (auto d: m->renderer()->triangles_drawables()) {
1045 if (d->is_visible())
1047 easy3d_debug_log_gl_error
1048 }
1049
1050 glDepthRange(0.0, 1.0);
1051 glDepthFunc(GL_LEQUAL);
1052 for (auto d: m->renderer()->lines_drawables()) {
1053 if (d->is_visible())
1055 easy3d_debug_log_gl_error
1056 }
1057 glDepthFunc(GL_LESS);
1058
1059 for (auto d: m->renderer()->points_drawables()) {
1060 if (d->is_visible())
1062 easy3d_debug_log_gl_error
1063 }
1064 }
1065
1066 for (auto d : drawables_) {
1067 if (d->is_visible())
1069 }
1070#endif
1071 }
1072
1073
1074
1075}
KeyFrameInterpolator * keyframe_interpolator() const
Get the keyframe interpolator.
Definition camera.h:336
@ PERSPECTIVE
Perspective camera.
Definition camera.h:144
@ ORTHOGRAPHIC
Orthographic camera.
Definition camera.h:145
void setSceneRadius(float radius)
Set the radius of the scene.
float sceneRadius() const
Returns the radius of the scene observed by the Camera.
Definition camera.h:565
void interpolateToLookAt(const vec3 &point)
Perform keyframe interpolation to look at a point.
vec3 projectedCoordinatesOf(const vec3 &src, const Frame *frame=nullptr) const
Returns the screen projected coordinates of a 3D point defined in the frame coordinate system....
void setScreenWidthAndHeight(int width, int height)
Set the screen width and height.
void setUpVector(const vec3 &up, bool noMove=true) const
Set the up vector of the camera.
void interpolateToFitScene()
Perform keyframe interpolation to fit the scene.
void setViewDirection(const vec3 &direction) const
Set the view direction of the camera.
ManipulatedCameraFrame * frame() const
Returns the ManipulatedFrame attached to the Camera.
Definition camera.h:623
The Frame class represents a coordinate system, defined by a position and an orientation.
Definition frame.h:78
vec3 position() const
Returns the position of the Frame.
void grow(const Point &p)
Add a point to this box. This will compute its new extent.
Definition box.h:276
void start_interpolation()
Starts the interpolation process.
Definition key_frame_interpolator.cpp:213
void stop_interpolation()
Stops an interpolation started with start_interpolation().
Definition key_frame_interpolator.cpp:245
void delete_path()
Removes all keyframes from the path.
Definition key_frame_interpolator.cpp:140
bool add_keyframe(const Frame &frame)
Appends a new keyframe to the path.
Definition key_frame_interpolator.cpp:74
@ NONE
No constraint.
Definition manipulated_frame.h:165
static Mat< N, M, T > identity()
Returns an identity matrix.
Definition mat.h:697
static bool init()
Initialize OpenGL.
Definition opengl_util.cpp:49
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 void terminate()
Destroy all textures and release their memory.
Definition texture_manager.cpp:147
static void single_shot(int delay, std::function< void(Args...)> const &func, Args... args)
Executes function func after delay milliseconds.
Definition timer.h:202
void update() const
Update the display (i.e., repaint).
Definition viewer.cpp:630
std::string simple_name(const std::string &path)
Gets file name without path but with extension (e.g, /a/b/c.Ext => c.Ext)
void setup_gl_debug_callback()
Set up a debug callback for OpenGL.
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
T distance(const Vec< N, T > &v1, const Vec< N, T > &v2)
Computes the distance between two vectors/points.
Definition vec.h:295
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 "window.h"
28
29#include <string>
30#include <iostream>
31
32#include <QFileDialog>
33#include <QDropEvent>
34#include <QMimeData>
35#include <QSettings>
36#include <QMessageBox>
37#include <QColorDialog>
38
39#include <easy3d/core/surface_mesh.h>
40#include <easy3d/core/graph.h>
41#include <easy3d/core/point_cloud.h>
42#include <easy3d/core/poly_mesh.h>
43#include <easy3d/fileio/point_cloud_io.h>
44#include <easy3d/fileio/graph_io.h>
45#include <easy3d/fileio/surface_mesh_io.h>
46#include <easy3d/fileio/poly_mesh_io.h>
47#include <easy3d/fileio/ply_reader_writer.h>
48#include <easy3d/fileio/point_cloud_io_ptx.h>
49#include <easy3d/util/file_system.h>
50#include <easy3d/util/logging.h>
51#include <easy3d/util/progress.h>
52#include <easy3d/algo/surface_mesh_components.h>
53#include <easy3d/algo/surface_mesh_topology.h>
54
55#include "viewer.h"
56
57#include <ui_window.h>
58
59
61
62
63
64 Window::Window(QWidget *parent)
65 : QMainWindow(parent), ui(new Ui::Window) {
66 ui->setupUi(this);
67
68 viewer_ = new Viewer(this);
69 connect(viewer_, SIGNAL(currentModelChanged()),
this, SLOT(onCurrentModelChanged()));
70 setCentralWidget(viewer_);
71
72 createActions();
73
74 setWindowIcon(QIcon(QString::fromStdString(":/resources/icons/ViewerQt.png")));
75 setFocusPolicy(Qt::StrongFocus);
76 setContextMenuPolicy(Qt::CustomContextMenu);
77 setAcceptDrops(true);
78
79 setBaseSize(1280, 960);
80
81
82 readSettings();
83 updateWindowTitle();
84 }
85
86
87 void Window::dragEnterEvent(QDragEnterEvent *e) {
88 if (e->mimeData()->hasUrls())
89 e->acceptProposedAction();
90 }
91
92
93 void Window::dropEvent(QDropEvent *e) {
94 if (e->mimeData()->hasUrls())
95 e->acceptProposedAction();
96
97 int count = 0;
98 foreach (const QUrl &url, e->mimeData()->urls()) {
99 const QString &fileName = url.toLocalFile();
100 if (open(fileName.toStdString()))
101 ++count;
102 }
103
104 if (count > 0)
105 viewer_->update();
106 }
107
108
109 bool Window::onOpen() {
110 const QStringList &fileNames = QFileDialog::getOpenFileNames(
111 this,
112 "Open file(s)",
113 curDataDirectory_,
114 "Supported formats (*.ply *.obj *.off *.stl *.sm *.geojson *.trilist *.bin *.las *.laz *.xyz *.bxyz *.vg *.bvg *.ptx *.plm *.pm *.mesh)\n"
115 "Surface Mesh (*.ply *.obj *.off *.stl *.sm *.geojson *.trilist)\n"
116 "Point Cloud (*.ply *.bin *.ptx *.las *.laz *.xyz *.bxyz *.vg *.bvg *.ptx)\n"
117 "Polyhedral Mesh (*.plm *.pm *.mesh)\n"
118 "Graph (*.ply)\n"
119 "All formats (*.*)"
120 );
121
122
123 QApplication::processEvents();
124
125 if (fileNames.empty())
126 return false;
127
128 int count = 0;
129 ProgressLogger progress(fileNames.size(), false, false);
130 for (const auto &name: fileNames) {
131 if (progress.is_canceled()) {
132 LOG(WARNING) << "opening files cancelled";
133 break;
134 }
135 if (open(name.toStdString()))
136 ++count;
137 progress.next();
138 }
139 if (count > 0)
140 viewer_->update();
141
142 return count > 0;
143 }
144
145
146 bool Window::onSave() {
147 const Model *model = viewer_->currentModel();
148 if (!model) {
149 std::cerr << "no model exists" << std::endl;
150 return false;
151 }
152
153 std::string default_file_name = model->name();
155 default_file_name += ".ply";
156
157 const QString &fileName = QFileDialog::getSaveFileName(
158 this,
159 "Open file(s)",
160 QString::fromStdString(default_file_name),
161 "Supported formats (*.ply *.obj *.off *.stl *.sm *.bin *.las *.laz *.xyz *.bxyz *.vg *.bvg *.plm *.pm *.mesh)\n"
162 "Surface Mesh (*.ply *.obj *.off *.stl *.sm)\n"
163 "Point Cloud (*.ply *.bin *.ptx *.las *.laz *.xyz *.bxyz *.vg *.bvg)\n"
164 "Polyhedral Mesh (*.plm *.pm *.mesh)\n"
165 "Graph (*.ply)\n"
166 "All formats (*.*)"
167 );
168
169 if (fileName.isEmpty())
170 return false;
171
172 bool saved = false;
173 if (dynamic_cast<const PointCloud *>(model)) {
174 const auto cloud = dynamic_cast<const PointCloud *>(model);
176 } else if (dynamic_cast<const SurfaceMesh *>(model)) {
177 const auto mesh = dynamic_cast<const SurfaceMesh *>(model);
179 } else if (dynamic_cast<const Graph *>(model)) {
180 const auto graph = dynamic_cast<const Graph *>(model);
182 }
183
184 if (saved) {
185 std::cout << "model successfully saved to: " << fileName.toStdString();
186 setCurrentFile(fileName);
187 return true;
188 }
189
190 return false;
191 }
192
193
194 Model *Window::open(const std::string &file_name) {
195 auto models = viewer_->models();
196 for (auto m: models) {
197 if (m->name() == file_name) {
198 LOG(WARNING) << "model already loaded: " << file_name;
199 return nullptr;
200 }
201 }
202
204 bool is_ply_mesh = false;
205 if (ext == "ply")
207
208 Model *model = nullptr;
209 if ((ext == "ply" && is_ply_mesh) || ext == "obj" || ext == "off" || ext == "stl" || ext == "sm" ||
210 ext == "plg") {
214 } else if (ext == "plm" || ext == "pm" || ext == "mesh") {
216 } else {
217 if (ext == "ptx") {
218 io::PointCloudIO_ptx serializer(file_name);
219 while (auto cloud = serializer.load_next())
220 viewer_->addModel(cloud);
221 } else
223 }
224
225 if (model) {
226 model->set_name(file_name);
227 viewer_->addModel(model);
228 setCurrentFile(QString::fromStdString(file_name));
229 }
230
231 return model;
232 }
233
234
235 void Window::onCurrentModelChanged() {
236 const Model *m = viewer_->currentModel();
237 if (m) {
238 viewer_->fitScreen(m);
239
240 const std::string &name = m->name();
241 setCurrentFile(QString::fromStdString(name));
242 } else
243 updateWindowTitle();
244 }
245
246
247 void Window::setCurrentFile(const QString &fileName) {
248 QString dir = fileName.left(fileName.lastIndexOf("/"));
250 curDataDirectory_ = dir;
251
252 setWindowModified(false);
253
254 if (!fileName.isEmpty()) {
255 recentFiles_.removeAll(fileName);
256 recentFiles_.prepend(fileName);
257 updateRecentFileActions();
258 }
259
260 updateWindowTitle();
261 }
262
263
264 void Window::onOpenRecentFile() {
265 if (okToContinue()) {
266 auto action = qobject_cast<QAction *>(sender());
267 if (action) {
268 const QString filename(action->data().toString());
269 if (open(filename.toStdString()))
270 viewer_->update();
271 }
272 }
273 }
274
275
276 void Window::onClearRecentFiles() {
277 recentFiles_.clear();
278 updateRecentFileActions();
279 }
280
281
282 void Window::saveSnapshot() {
283 const Model *model = viewer_->currentModel();
284
285 const bool overwrite = false;
286 std::string default_file_name("untitled.png");
287 if (model)
289 const QString fileName = QFileDialog::getSaveFileName(
290 this,
291 "Please choose a file name",
292 QString::fromStdString(default_file_name),
293 "Image Files (*.png *.jpg *.bmp *.ppm)\n"
294 "PNG (*.png)\n"
295 "JPG (*.jpg)\n"
296 "Windows Bitmap (*.bmp)\n"
297 "24bit RGB Bitmap (*.ppm)\n"
298 "All Files (*.*)",
299 nullptr,
300 overwrite ? QFileDialog::DontConfirmOverwrite : QFileDialog::Option()
301 );
302
303 QApplication::processEvents();
304
305 if (fileName.isEmpty())
306 return;
307
308 viewer_->saveSnapshot(fileName);
309 }
310
311
312 void Window::setBackgroundColor() {
313 const vec4 &c = viewer_->backGroundColor();
314 QColor orig(static_cast<int>(c.r * 255), static_cast<int>(c.g * 255), static_cast<int>(c.b * 255),
315 static_cast<int>(c.a * 255));
316 const QColor &color = QColorDialog::getColor(orig, this);
317 if (color.isValid()) {
318 const vec4 newColor(
static_cast<float>(color.redF()),
static_cast<float>(color.greenF()),
static_cast<float>(color.blueF()),
static_cast<float>(color.alphaF()));
319 viewer_->setBackgroundColor(newColor);
320 viewer_->update();
321 }
322 }
323
324
325 bool Window::okToContinue() {
326 if (isWindowModified()) {
327 QMessageBox::StandardButton r = QMessageBox::warning(
328 this,
329 tr("Viewer"),
330 tr("The model has been modified.\n"
331 "Do you want to save your changes?"),
332 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
333 QMessageBox::Yes
334 );
335 if (r == QMessageBox::Yes)
336 return onSave();
337 else if (r == QMessageBox::Cancel)
338 return false;
339 }
340 return true;
341 }
342
343
344 void Window::onAbout() {
345 const std::string name = "<h3>" + std::string(EXAMPLE_TITLE) + "</h3>";
346 QString title = QMessageBox::tr(name.c_str());
347
348 QString text = QMessageBox::tr(
349 "<p>This viewer shows how to use Qt for GUI creation and event handling</p>"
350 "<p>Liangliang Nan<br>"
351 "<a href=\"mailto:liangliang.nan@gmail.com\">liangliang.nan@gmail.com</a><br>"
352 "<a href=\"https://3d.bk.tudelft.nl/liangliang/\">https://3d.bk.tudelft.nl/liangliang/</a></p>"
353 );
354
355
356 QMessageBox::about(this, "About Viewer", title + text);
357 }
358
359
360 void Window::readSettings() {
361 QSettings settings("liangliang.nan@gmail.com", "Viewer");
362 recentFiles_ = settings.value("recentFiles").toStringList();
363 updateRecentFileActions();
364 curDataDirectory_ = settings.value("currentDirectory").toString();
365 }
366
367
368 void Window::writeSettings() {
369 QSettings settings("liangliang.nan@gmail.com", "Viewer");
370 settings.setValue("recentFiles", recentFiles_);
372 settings.setValue("currentDirectory", curDataDirectory_);
373 }
374
375
376 void Window::updateWindowTitle() {
377 Model *model = viewer_->currentModel();
378
379 QString title = QString::fromStdString(EXAMPLE_TITLE);
380#ifndef NDEBUG
381 title += " (Debug Version)";
382#endif
383
384 QString fileName("Untitled");
385 if (model)
386 fileName = QString::fromStdString(model->name());
387
388 title = tr("%1[*] - %2").arg(strippedName(fileName), title);
389 setWindowTitle(title);
390 }
391
392
393 void Window::closeEvent(QCloseEvent *event) {
394 if (okToContinue()) {
395 writeSettings();
396 event->accept();
397 } else {
398 event->ignore();
399 }
400 }
401
402
403 void Window::updateRecentFileActions() {
404 QMutableStringListIterator i(recentFiles_);
405 while (i.hasNext()) {
406 if (!QFile::exists(i.next()))
407 i.remove();
408 }
409
410 for (int j = 0; j < MaxRecentFiles; ++j) {
411 if (j < recentFiles_.count()) {
412 QString text = tr("&%1 %2").arg(j + 1).arg(strippedName(recentFiles_[j]));
413 actionsRecentFile[j]->setText(text);
414 actionsRecentFile[j]->setData(recentFiles_[j]);
415 actionsRecentFile[j]->setVisible(true);
416 } else {
417 actionsRecentFile[j]->setVisible(false);
418 }
419 }
420
421 actionSeparator->setVisible(!recentFiles_.isEmpty());
422 }
423
424
425 QString Window::strippedName(const QString &fullFileName) {
426 return QFileInfo(fullFileName).fileName();
427 }
428
429
430 void Window::createActions() {
431
432 createActionsForFileMenu();
433
434
435 createActionsForViewMenu();
436
437
438 createActionsForTopologyMenu();
439
440
441 connect(ui->actionAbout, SIGNAL(triggered()),
this, SLOT(onAbout()));
442 }
443
444
445 void Window::createActionsForFileMenu() {
446 connect(ui->actionOpen, SIGNAL(triggered()),
this, SLOT(onOpen()));
447 connect(ui->actionSave, SIGNAL(triggered()),
this, SLOT(onSave()));
448
449 actionSeparator = ui->menuFile->addSeparator();
450
451 QList<QAction*> actions;
452 for (auto& action : actionsRecentFile) {
453 action = new QAction(this);
454 action->setVisible(false);
455 connect(action, SIGNAL(triggered()),
this, SLOT(onOpenRecentFile()));
456 actions.push_back(action);
457 }
458 ui->menuRecentFiles->insertActions(ui->actionClearRecentFiles, actions);
459 ui->menuRecentFiles->insertSeparator(ui->actionClearRecentFiles);
460 connect(ui->actionClearRecentFiles, SIGNAL(triggered()),
this, SLOT(onClearRecentFiles()));
461
462 connect(ui->actionExit, SIGNAL(triggered()),
this, SLOT(close()));
463 ui->actionExit->setShortcut(QString("Ctrl+Q"));
464 }
465
466
467 void Window::createActionsForViewMenu() {
468 connect(ui->actionSnapshot, SIGNAL(triggered()),
this, SLOT(saveSnapshot()));
469
470 ui->menuView->addSeparator();
471
472 connect(ui->actionSetBackgroundColor, SIGNAL(triggered()),
this, SLOT(setBackgroundColor()));
473 }
474
475
476 void Window::createActionsForTopologyMenu() {
477 connect(ui->actionTopologyStatistics, SIGNAL(triggered()),
this, SLOT(reportTopologyStatistics()));
478 }
479
480
481 void Window::reportTopologyStatistics() {
482 auto mesh = dynamic_cast<SurfaceMesh *>(viewer()->currentModel());
483 if (!mesh)
484 return;
485
487 if (simple_name.empty())
488 std::cout << "#elements in model (with unknown name): ";
489 else
491
492 std::cout << "#face = " << mesh->n_faces() << ", #vertex = " << mesh->n_vertices() << ", #edge = "
493 << mesh->n_edges() << std::endl;
494
495
496 std::size_t count = 0;
497 for (auto v: mesh->vertices()) {
498 if (mesh->is_isolated(v))
499 ++count;
500 }
501 if (count > 0)
502 std::cout << "#isolated vertices: " << count << std::endl;
503
505 std::cout << "#connected component: " << components.size() << std::endl;
506
507 const std::size_t num = 10;
508 if (components.size() > num)
509 std::cout << "\ttopology of the first " << num << " components:" << std::endl;
510
511 for (std::size_t i = 0; i < std::min(components.size(), num); ++i) {
512 const SurfaceMeshComponent &comp = components[i];
513 SurfaceMeshTopology topo(&comp);
514 std::string type = "unknown";
515 if (topo.is_sphere())
516 type = "sphere";
517 else if (topo.is_disc())
518 type = "disc";
519 else if (topo.is_cylinder())
520 type = "cylinder";
521 else if (topo.is_torus())
522 type = "torus";
523 else if (topo.is_closed())
524 type = "unknown closed";
525
526 std::cout << "\t\t" << i << ": "
527 << type
528 << ", #face = " << comp.n_faces() << ", #vertex = " << comp.n_vertices() << ", #edge = "
529 << comp.n_edges()
530 << ", #border = " << topo.number_of_borders();
531 if (topo.number_of_borders() == 1)
532 std::cout << ", border size = " << topo.largest_border_size();
533 else if (topo.number_of_borders() > 1)
534 std::cout << ", largest border size = " << topo.largest_border_size();
535 std::cout << std::endl;
536 }
537 }
538
539
540
541}
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
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
static std::vector< SurfaceMeshComponent > extract(SurfaceMesh *mesh, bool descending=true)
Extracts connected components from the given mesh.
Definition surface_mesh_components.cpp:187
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 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 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,...
std::string extension(const std::string &path, bool lower=true)
Query the file extension without dot (e.g., /a/b/c.Ext => Ext).
bool is_directory(const std::string &path)
Tests if 'path' is an existing directory.