This example shows how to create a simple viewer using Qt.
31#include <QOpenGLContext>
32#include <QOpenGLFunctions>
33#include <QOpenGLFramebufferObjectFormat>
34#include <QApplication>
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>
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),
70 setFocusPolicy(Qt::WheelFocus);
71 setMouseTracking(
true);
75 camera_->setUpVector(
vec3(0, 0, 1));
76 camera_->setViewDirection(
vec3(-1, 0, 0));
77 camera_->showEntireScene();
92 LOG(INFO) <<
"viewer terminated. Bye!";
96 void Viewer::cleanup() {
98 delete drawable_axes_;
101 for (
auto m: models_)
105 for (
auto d : drawables_)
114 void Viewer::init() {
127 void Viewer::initializeGL() {
128 QOpenGLWidget::initializeGL();
129 initializeOpenGLFunctions();
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");
144 glEnable(GL_DEPTH_TEST);
146 glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
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);
157 glGetIntegerv(GL_MAJOR_VERSION, &major);
159 glGetIntegerv(GL_MINOR_VERSION, &minor);
160 if (major * 10 + minor < 32) {
161 throw std::runtime_error(
"Viewer requires at least OpenGL 3.2");
164#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
165 dpi_scaling_ =
static_cast<float>(devicePixelRatioF());
167 dpi_scaling_ =
static_cast<float>(devicePixelRatio());
169 VLOG(1) <<
"DPI scaling: " << dpiScaling();
178 texter_ =
new TextRenderer(dpiScaling());
186 std::cout << usage() << std::endl;
192 void Viewer::resizeGL(
int w,
int h) {
193 QOpenGLWidget::resizeGL(w, h);
203 void Viewer::setBackgroundColor(
const vec4 &c) {
204 background_color_ = c;
207 glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
212 void Viewer::mousePressEvent(QMouseEvent *e) {
213 pressed_button_ = e->button();
214 mouse_previous_pos_ = e->pos();
215 mouse_pressed_pos_ = e->pos();
217 camera_->frame()->action_start();
218 if (e->modifiers() == Qt::ShiftModifier) {
219 if (e->button() == Qt::LeftButton) {
221 const vec3 &p = pointUnderPixel(e->pos(), found);
224 camera_->setPivotPoint(p);
227 show_pivot_point_ =
true;
229 show_pivot_point_ =
false;
233 camera_->setPivotPoint(camera_->sceneCenter());
234 show_pivot_point_ =
false;
236 }
else if (e->button() == Qt::RightButton) {
238 camera_->setPivotPoint(camera_->sceneCenter());
239 show_pivot_point_ =
false;
243 QOpenGLWidget::mousePressEvent(e);
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);
256 camera_->frame()->action_end();
258 pressed_button_ = Qt::NoButton;
259 mouse_pressed_pos_ = QPoint(0, 0);
261 QOpenGLWidget::mouseReleaseEvent(e);
266 void Viewer::mouseMoveEvent(QMouseEvent *e) {
267 int x = e->pos().x(), y = e->pos().y();
269 if (x < 0 || x >
width() || y < 0 || y >
height()) {
274 if (pressed_button_ != Qt::NoButton) {
275 if (e->modifiers() == Qt::ControlModifier) {
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) {
286 camera_->frame()->action_zoom(dy > 0 ? 1 : -1, camera_);
291 mouse_previous_pos_ = e->pos();
292 QOpenGLWidget::mouseMoveEvent(e);
296 void Viewer::mouseDoubleClickEvent(QMouseEvent *e) {
297 QOpenGLWidget::mouseDoubleClickEvent(e);
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_);
309 QOpenGLWidget::wheelEvent(e);
314 bool Viewer::saveSnapshot(
const QString &file_name) {
317 int w =
static_cast<int>(
static_cast<float>(
width()) * dpiScaling());
318 int h =
static_cast<int>(
static_cast<float>(
height()) * dpiScaling());
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);
327 glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
328 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
334 const QImage &image = fbo->toImage();
337 glClearColor(background_color_[0], background_color_[1], background_color_[2], background_color_[3]);
341 return image.save(file_name);
345 Model *Viewer::currentModel()
const {
348 if (model_idx_ < models_.size())
349 return models_[model_idx_];
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) {
386 drawable_axes_->set_visible(!drawable_axes_->is_visible());
387 }
else if (e->key() == Qt::Key_C && e->modifiers() == Qt::NoModifier) {
389 fitScreen(currentModel());
390 }
else if (e->key() == Qt::Key_F && e->modifiers() == Qt::NoModifier) {
392 }
else if (e->key() == Qt::Key_P && e->modifiers() == Qt::NoModifier) {
397 }
else if (e->key() == Qt::Key_Space && e->modifiers() == Qt::NoModifier) {
400 frame.setTranslation(camera_->pivotPoint());
401 camera_->frame()->alignWithFrame(&frame,
true);
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_);
411 else if (e->key() == Qt::Key_K && e->modifiers() == Qt::AltModifier) {
418 }
else if (e->key() == Qt::Key_D && e->modifiers() == Qt::ControlModifier) {
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) {
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;
437 d->set_line_width(size);
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);
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;
453 d->set_point_size(size);
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);
463 }
else if (e->key() == Qt::Key_Comma && e->modifiers() == Qt::NoModifier) {
464 int pre_idx = model_idx_;
468 model_idx_ = int((model_idx_ - 1 + models_.size()) % models_.size());
469 if (model_idx_ != pre_idx) {
471 LOG(INFO) <<
"current model: " << model_idx_ <<
", " << models_[model_idx_]->name();
473 }
else if (e->key() == Qt::Key_Period && e->modifiers() == Qt::NoModifier) {
474 int pre_idx = model_idx_;
478 model_idx_ = int((model_idx_ + 1) % models_.size());
479 if (model_idx_ != pre_idx) {
481 LOG(INFO) <<
"current model: " << model_idx_ <<
", " << models_[model_idx_]->name();
483 }
else if (e->key() == Qt::Key_Delete && e->modifiers() == Qt::NoModifier) {
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");
490 drawable->set_visible(!drawable->is_visible());
492 }
else if (e->key() == Qt::Key_V && e->modifiers() == Qt::NoModifier) {
493 if (currentModel()) {
494 auto drawable = currentModel()->renderer()->get_points_drawable(
"vertices");
496 drawable->set_visible(!drawable->is_visible());
498 }
else if (e->key() == Qt::Key_B && e->modifiers() == Qt::NoModifier) {
499 auto mesh =
dynamic_cast<SurfaceMesh *
>(currentModel());
501 auto drawable = mesh->renderer()->get_lines_drawable(
"borders");
503 drawable->set_visible(!drawable->is_visible());
505 }
else if (e->key() == Qt::Key_L && e->modifiers() == Qt::NoModifier) {
506 auto mesh =
dynamic_cast<SurfaceMesh *
>(currentModel());
508 auto drawable = mesh->renderer()->get_points_drawable(
"locks");
510 drawable->set_visible(!drawable->is_visible());
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");
516 drawable->set_smooth_shading(!drawable->smooth_shading());
519 }
else if (e->key() == Qt::Key_D && e->modifiers() == Qt::NoModifier) {
520 if (currentModel()) {
521 std::ostream &output = std::cout;
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;
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);
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);
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);
554 currentModel()->property_stats(output);
556 }
else if (e->key() == Qt::Key_R && e->modifiers() == Qt::NoModifier) {
561 QOpenGLWidget::keyPressEvent(e);
566 void Viewer::keyReleaseEvent(QKeyEvent *e) {
567 QOpenGLWidget::keyReleaseEvent(e);
572 void Viewer::timerEvent(QTimerEvent *e) {
573 QOpenGLWidget::timerEvent(e);
578 void Viewer::closeEvent(QCloseEvent *e) {
580 QOpenGLWidget::closeEvent(e);
584 std::string Viewer::usage()
const {
585 return " ------------------------------------------------------------------\n"
586 " Easy3D viewer usage: \n"
587 " ------------------------------------------------------------------\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"
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";
623 void Viewer::addModel(Model *model) {
625 LOG(WARNING) <<
"model is nullptr";
628 for (
auto m: models_) {
630 LOG(WARNING) <<
"model has already been added to the viewer.";
635 if (model->empty()) {
636 LOG(WARNING) <<
"model does not have vertices. Only complete model can be added to the viewer.";
641 model->set_renderer(std::make_shared<Renderer>(model));
644 int pre_idx = model_idx_;
645 models_.push_back(model);
646 model_idx_ =
static_cast<int>(models_.size()) - 1;
648 if (model_idx_ != pre_idx) {
649 emit currentModelChanged();
651 LOG(INFO) <<
"current model: " << model_idx_ <<
", " << models_[model_idx_]->name();
656 void Viewer::deleteModel(Model *model) {
658 LOG(WARNING) <<
"model is nullptr";
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();
670 model_idx_ =
static_cast<int>(models_.size()) - 1;
672 std::cout <<
"model deleted: " << name << std::endl;
674 LOG(WARNING) <<
"no such model: " << model->name();
676 if (model_idx_ != pre_idx) {
677 emit currentModelChanged();
679 LOG(INFO) <<
"current model: " << model_idx_ <<
", " << models_[model_idx_]->name();
684 bool Viewer::addDrawable(Drawable *drawable) {
686 LOG(WARNING) <<
"drawable is nullptr";
689 for (
auto d : drawables_) {
691 LOG(WARNING) <<
"drawable has already been added to the viewer.";
696 drawables_.push_back(drawable);
701 bool Viewer::deleteDrawable(Drawable *drawable) {
703 LOG(WARNING) <<
"drawable is nullptr";
707 for (
auto it = drawables_.begin(); it != drawables_.end(); ++it) {
708 if (*it == drawable) {
710 drawables_.erase(it);
716 LOG(WARNING) <<
"no such drawable: " << drawable->name();
721 void Viewer::fitScreen(
const Model *model) {
722 if (!model && models_.empty() && drawables_.empty())
727 box = model->bounding_box();
729 for (
auto m: models_)
730 box.grow(m->bounding_box());
731 for (
auto d : drawables_)
732 box.grow(d->bounding_box());
734 camera_->setSceneBoundingBox(box.min_point(), box.max_point());
735 camera_->showEntireScene();
740 vec3 Viewer::pointUnderPixel(
const QPoint &p,
bool &found) {
765 glGetIntegerv(GL_VIEWPORT, viewport);
766 const int w = viewport[2];
767 const int h = viewport[3];
770 QOpenGLFramebufferObjectFormat format;
771 format.setAttachment(QOpenGLFramebufferObject::Depth);
772 format.setSamples(0);
773 QOpenGLFramebufferObject fbo(w, h, format);
777 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
778 std::cerr <<
"Framebuffer is not complete!" << std::endl;
779 return vec3(0, 0, 0);
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);
794 int gly =
height() - 1 - p.y();
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);
805 if (!last_enable_depth_test)
806 glDisable(GL_DEPTH_TEST);
810 found = depth < 1.0f;
813 return camera_->unprojectedCoordinatesOf(
vec3(
static_cast<float>(p.x()),
static_cast<float>(p.y()), depth));
820 void Viewer::paintGL() {
821 easy3d_debug_log_gl_error
830 static bool queried =
false;
833 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &samples_);
834 easy3d_debug_log_frame_buffer_error
836 glGetIntegerv(GL_SAMPLES, &samples_); easy3d_debug_log_gl_error
839 int samples = QSurfaceFormat::defaultFormat().samples();
841 glGetIntegerv(GL_MAX_SAMPLES, &max_num);
844 LOG(WARNING) <<
"MSAA is not available (" <<
samples <<
" samples requested)";
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 "
864 void Viewer::drawCornerAxes() {
867 std::vector<ShaderProgram::Attribute> attributes;
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,
886 shape::create_cone(0.06, 20,
vec3(0, base, 0),
vec3(0, base + head, 0),
vec3(0, 1, 0), points, normals,
888 shape::create_cone(0.06, 20,
vec3(0, 0, base),
vec3(0, 0, base + head),
vec3(0, 0, 1), points, normals,
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);
897 if (!drawable_axes_->is_visible())
902 glGetIntegerv(GL_VIEWPORT, viewport);
904 static const int corner_frame_size =
static_cast<int>(100 * dpiScaling());
905 glViewport(0, 0, corner_frame_size, corner_frame_size);
909 glDepthRangef(0, 0.01f);
912 const mat4 &view = camera_->orientation().inverse().matrix();
913 const mat4 &MVP = proj * view;
916 const vec3 &wCamPos = camera_->position();
919 const mat4 &MV = camera_->modelViewMatrix();
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();
946 glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
947 glDepthRangef(0.0f, 1.0f);
951 void Viewer::preDraw() {
955 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
959 void Viewer::postDraw() {
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);
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));
976 texter_->draw(fps_string.toStdString(), offset, 50.0f * dpiScaling(), 16, 1);
979 if (show_pivot_point_) {
982 std::vector<ShaderProgram::Attribute> attributes;
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)
997 drawable.update_vertex_buffer(points);
1002 glDisable(GL_DEPTH_TEST);
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));
1009 glEnable(GL_DEPTH_TEST);
1016 void Viewer::draw() {
1019 glEnable(GL_SCISSOR_TEST);
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())
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())
1032 glScissor(viewport[0], viewport[1], viewport[2], viewport[3]);
1036 easy3d_debug_log_gl_error
1038 for (
const auto m: models_) {
1039 if (!m->renderer()->is_visible())
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
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
1057 glDepthFunc(GL_LESS);
1059 for (
auto d: m->renderer()->points_drawables()) {
1060 if (d->is_visible())
1062 easy3d_debug_log_gl_error
1066 for (
auto d : drawables_) {
1067 if (d->is_visible())
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
bool is_interpolation_started() const
Returns whether the interpolation is being performed.
Definition key_frame_interpolator.h:342
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:161
static void single_shot(int delay, std::function< void(Args...)> const &func, Args... args)
Executes function func after delay milliseconds.
Definition timer.h:202
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
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
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
Vec< 4, float > vec4
A 4D point/vector of float type.
Definition types.h:46
int connect(SIGNAL *signal, FUNCTION const &slot)
Connects a function to the signal.
Definition signal.h:200
37#include <QColorDialog>
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>
64 Window::Window(QWidget *parent)
65 : QMainWindow(parent), ui(
new Ui::Window) {
68 viewer_ =
new Viewer(
this);
69 connect(viewer_, SIGNAL(currentModelChanged()),
this, SLOT(onCurrentModelChanged()));
70 setCentralWidget(viewer_);
74 setWindowIcon(QIcon(QString::fromStdString(
":/resources/icons/ViewerQt.png")));
75 setFocusPolicy(Qt::StrongFocus);
76 setContextMenuPolicy(Qt::CustomContextMenu);
79 setBaseSize(1280, 960);
87 void Window::dragEnterEvent(QDragEnterEvent *e) {
88 if (e->mimeData()->hasUrls())
89 e->acceptProposedAction();
93 void Window::dropEvent(QDropEvent *e) {
94 if (e->mimeData()->hasUrls())
95 e->acceptProposedAction();
98 foreach (
const QUrl &url, e->mimeData()->urls()) {
99 const QString &fileName = url.toLocalFile();
100 if (open(fileName.toStdString()))
109 bool Window::onOpen() {
110 const QStringList &fileNames = QFileDialog::getOpenFileNames(
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"
123 QApplication::processEvents();
125 if (fileNames.empty())
129 ProgressLogger progress(fileNames.size(),
false,
false);
130 for (
const auto &name: fileNames) {
131 if (progress.is_canceled()) {
132 LOG(WARNING) <<
"opening files cancelled";
135 if (open(name.toStdString()))
146 bool Window::onSave() {
147 const Model *model = viewer_->currentModel();
149 std::cerr <<
"no model exists" << std::endl;
153 std::string default_file_name = model->name();
155 default_file_name +=
".ply";
157 const QString &fileName = QFileDialog::getSaveFileName(
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"
169 if (fileName.isEmpty())
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);
185 std::cout <<
"model successfully saved to: " << fileName.toStdString();
186 setCurrentFile(fileName);
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;
204 bool is_ply_mesh =
false;
208 Model *model =
nullptr;
209 if ((ext ==
"ply" && is_ply_mesh) || ext ==
"obj" || ext ==
"off" || ext ==
"stl" || ext ==
"sm" ||
214 }
else if (ext ==
"plm" || ext ==
"pm" || ext ==
"mesh") {
218 io::PointCloudIO_ptx serializer(file_name);
219 while (
auto cloud = serializer.load_next())
220 viewer_->addModel(cloud);
226 model->set_name(file_name);
227 viewer_->addModel(model);
228 setCurrentFile(QString::fromStdString(file_name));
235 void Window::onCurrentModelChanged() {
236 const Model *m = viewer_->currentModel();
238 viewer_->fitScreen(m);
240 const std::string &name = m->name();
241 setCurrentFile(QString::fromStdString(name));
247 void Window::setCurrentFile(
const QString &fileName) {
248 QString dir = fileName.left(fileName.lastIndexOf(
"/"));
250 curDataDirectory_ = dir;
252 setWindowModified(
false);
254 if (!fileName.isEmpty()) {
255 recentFiles_.removeAll(fileName);
256 recentFiles_.prepend(fileName);
257 updateRecentFileActions();
264 void Window::onOpenRecentFile() {
265 if (okToContinue()) {
266 auto action = qobject_cast<QAction *>(sender());
268 const QString filename(action->data().toString());
269 if (open(filename.toStdString()))
276 void Window::onClearRecentFiles() {
277 recentFiles_.clear();
278 updateRecentFileActions();
282 void Window::saveSnapshot() {
283 const Model *model = viewer_->currentModel();
285 const bool overwrite =
false;
286 std::string default_file_name(
"untitled.png");
289 const QString fileName = QFileDialog::getSaveFileName(
291 "Please choose a file name",
292 QString::fromStdString(default_file_name),
293 "Image Files (*.png *.jpg *.bmp *.ppm)\n"
296 "Windows Bitmap (*.bmp)\n"
297 "24bit RGB Bitmap (*.ppm)\n"
300 overwrite ? QFileDialog::DontConfirmOverwrite : QFileDialog::Option()
303 QApplication::processEvents();
305 if (fileName.isEmpty())
308 viewer_->saveSnapshot(fileName);
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);
325 bool Window::okToContinue() {
326 if (isWindowModified()) {
327 QMessageBox::StandardButton r = QMessageBox::warning(
330 tr(
"The model has been modified.\n"
331 "Do you want to save your changes?"),
332 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
335 if (r == QMessageBox::Yes)
337 else if (r == QMessageBox::Cancel)
344 void Window::onAbout() {
345 const std::string name =
"<h3>" + std::string(EXAMPLE_TITLE) +
"</h3>";
346 QString title = QMessageBox::tr(name.c_str());
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>"
356 QMessageBox::about(
this,
"About Viewer", title + text);
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();
368 void Window::writeSettings() {
369 QSettings settings(
"liangliang.nan@gmail.com",
"Viewer");
370 settings.setValue(
"recentFiles", recentFiles_);
372 settings.setValue(
"currentDirectory", curDataDirectory_);
376 void Window::updateWindowTitle() {
377 Model *model = viewer_->currentModel();
379 QString title = QString::fromStdString(EXAMPLE_TITLE);
381 title +=
" (Debug Version)";
384 QString fileName(
"Untitled");
386 fileName = QString::fromStdString(model->name());
388 title = tr(
"%1[*] - %2").arg(strippedName(fileName), title);
389 setWindowTitle(title);
393 void Window::closeEvent(QCloseEvent *event) {
394 if (okToContinue()) {
403 void Window::updateRecentFileActions() {
404 QMutableStringListIterator i(recentFiles_);
405 while (i.hasNext()) {
406 if (!QFile::exists(i.next()))
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);
417 actionsRecentFile[j]->setVisible(
false);
421 actionSeparator->setVisible(!recentFiles_.isEmpty());
425 QString Window::strippedName(
const QString &fullFileName) {
426 return QFileInfo(fullFileName).fileName();
430 void Window::createActions() {
432 createActionsForFileMenu();
435 createActionsForViewMenu();
438 createActionsForTopologyMenu();
441 connect(ui->actionAbout, SIGNAL(triggered()),
this, SLOT(onAbout()));
445 void Window::createActionsForFileMenu() {
446 connect(ui->actionOpen, SIGNAL(triggered()),
this, SLOT(onOpen()));
447 connect(ui->actionSave, SIGNAL(triggered()),
this, SLOT(onSave()));
449 actionSeparator = ui->menuFile->addSeparator();
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);
458 ui->menuRecentFiles->insertActions(ui->actionClearRecentFiles, actions);
459 ui->menuRecentFiles->insertSeparator(ui->actionClearRecentFiles);
460 connect(ui->actionClearRecentFiles, SIGNAL(triggered()),
this, SLOT(onClearRecentFiles()));
462 connect(ui->actionExit, SIGNAL(triggered()),
this, SLOT(close()));
463 ui->actionExit->setShortcut(QString(
"Ctrl+Q"));
467 void Window::createActionsForViewMenu() {
468 connect(ui->actionSnapshot, SIGNAL(triggered()),
this, SLOT(saveSnapshot()));
470 ui->menuView->addSeparator();
472 connect(ui->actionSetBackgroundColor, SIGNAL(triggered()),
this, SLOT(setBackgroundColor()));
476 void Window::createActionsForTopologyMenu() {
477 connect(ui->actionTopologyStatistics, SIGNAL(triggered()),
this, SLOT(reportTopologyStatistics()));
481 void Window::reportTopologyStatistics() {
482 auto mesh =
dynamic_cast<SurfaceMesh *
>(viewer()->currentModel());
487 if (simple_name.empty())
488 std::cout <<
"#elements in model (with unknown name): ";
492 std::cout <<
"#face = " << mesh->n_faces() <<
", #vertex = " << mesh->n_vertices() <<
", #edge = "
493 << mesh->n_edges() << std::endl;
496 std::size_t count = 0;
497 for (
auto v: mesh->vertices()) {
498 if (mesh->is_isolated(v))
502 std::cout <<
"#isolated vertices: " << count << std::endl;
505 std::cout <<
"#connected component: " << components.size() << std::endl;
507 const std::size_t num = 10;
508 if (components.size() > num)
509 std::cout <<
"\ttopology of the first " << num <<
" components:" << std::endl;
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())
517 else if (topo.is_disc())
519 else if (topo.is_cylinder())
521 else if (topo.is_torus())
523 else if (topo.is_closed())
524 type =
"unknown closed";
526 std::cout <<
"\t\t" << i <<
": "
528 <<
", #face = " << comp.n_faces() <<
", #vertex = " << comp.n_vertices() <<
", #edge = "
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;
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.